/**
 * 项目名：化工企业安全生产信息化管理平台
 * 日期：    2020-03-01 22:19:07
 * Copyright (c) 2015- -版权所有
 */

package com.joysuch.wwyt.edu.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.google.common.base.Objects;
import com.google.common.collect.Lists;
import com.joysuch.wwyt.alert.service.AlertMessageService;
import com.joysuch.wwyt.alert.service.NoticeWaitingTriggerListService;
import com.joysuch.wwyt.bp.bean.BpBaseUserBean;
import com.joysuch.wwyt.bp.constant.AppNotifyContentPre;
import com.joysuch.wwyt.bp.entity.BpDepart;
import com.joysuch.wwyt.bp.entity.BpJob;
import com.joysuch.wwyt.bp.repository.BpDepartDao;
import com.joysuch.wwyt.bp.repository.BpJobDao;
import com.joysuch.wwyt.common.constant.Constant;
import com.joysuch.wwyt.common.entity.CommonBusinessNotice;
import com.joysuch.wwyt.common.entity.CommonType;
import com.joysuch.wwyt.common.enums.AppBusinessNoticeTypes;
import com.joysuch.wwyt.common.enums.CommonTypes;
import com.joysuch.wwyt.common.service.CommonBusinessNoticeService;
import com.joysuch.wwyt.common.service.CommonIndexNotifyService;
import com.joysuch.wwyt.common.service.CommonTypeService;
import com.joysuch.wwyt.config.RedisClient;
import com.joysuch.wwyt.core.bean.BpUserBean;
import com.joysuch.wwyt.core.common.ajax.ResultBean;
import com.joysuch.wwyt.core.common.util.DateTimeUtil;
import com.joysuch.wwyt.core.common.util.PageDataUtils;
import com.joysuch.wwyt.core.common.util.PageDataUtils.PageDataConvert;
import com.joysuch.wwyt.core.common.util.SimpleDateFormatCache;
import com.joysuch.wwyt.core.entity.BaseConfig;
import com.joysuch.wwyt.core.entity.BaseImageRepository;
import com.joysuch.wwyt.core.entity.BaseUser;
import com.joysuch.wwyt.core.enums.BaseConfigCodes;
import com.joysuch.wwyt.core.enums.Status;
import com.joysuch.wwyt.core.exception.ApiBusinessException;
import com.joysuch.wwyt.core.exception.BusinessRestrictionsException;
import com.joysuch.wwyt.core.mapper.BaseUserMapper;
import com.joysuch.wwyt.core.repository.BaseUserDao;
import com.joysuch.wwyt.core.service.*;
import com.joysuch.wwyt.core.support.Context;
import com.joysuch.wwyt.edu.bean.*;
import com.joysuch.wwyt.edu.bean.EduDoExamBean.Question;
import com.joysuch.wwyt.edu.bean.EduDoExamBean.QuestionItem;
import com.joysuch.wwyt.edu.bean.EduDoExamResultBean.ExamResult;
import com.joysuch.wwyt.edu.bean.EduExamCenterListBean.AppSearchEduExamRecord;
import com.joysuch.wwyt.edu.bean.EduExamCenterListBean.SearchEduExamCenter;
import com.joysuch.wwyt.edu.bean.EduExamListBean.SearchEduExam;
import com.joysuch.wwyt.edu.bean.EduExamUserRecordBean.UserRecord;
import com.joysuch.wwyt.edu.bean.vo.ExamUserVo;
import com.joysuch.wwyt.edu.entity.*;
import com.joysuch.wwyt.edu.entity.dsl.*;
import com.joysuch.wwyt.edu.enums.*;
import com.joysuch.wwyt.edu.enums.ExamJoinInTargetTypes;
import com.joysuch.wwyt.edu.enums.ExamStates;
import com.joysuch.wwyt.edu.enums.QuestionTypes;
import com.joysuch.wwyt.edu.enums.TakeExamStates;
import com.joysuch.wwyt.edu.mapper.EduExamMapper;
import com.joysuch.wwyt.edu.mapper.EduExamRecordsMapper;
import com.joysuch.wwyt.edu.mapper.EduTrainningRecordMapper;
import com.joysuch.wwyt.edu.mapper.EduTrainningRecordUserMapper;
import com.joysuch.wwyt.edu.repository.*;
import com.joysuch.wwyt.edu.service.*;
import com.joysuch.wwyt.mh.DateUtils;
import com.joysuch.wwyt.repeatsubmit.NoRepeatSubmit;
import com.joysuch.wwyt.util.PoiUtil;
import com.joysuch.wwyt.util.RotateImageUtil;
import com.querydsl.core.types.ExpressionUtils;
import com.querydsl.core.types.Predicate;
import com.querydsl.jpa.impl.JPAQueryFactory;
import javafx.util.Pair;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.hssf.usermodel.*;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xssf.usermodel.*;
import org.redisson.api.RKeys;
import org.redisson.api.RList;
import org.redisson.api.RScoredSortedSet;
import org.redisson.api.RedissonClient;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.util.ObjectUtils;

import javax.imageio.ImageIO;
import javax.persistence.Tuple;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.transaction.Transactional;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.URLEncoder;
import java.text.MessageFormat;
import java.text.NumberFormat;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import static cn.afterturn.easypoi.util.PoiCellUtil.getCellValue;

@Service
@Transactional
@Slf4j
public class EduExamServiceImpl implements EduExamService {

    @Autowired
    private EduExamDao dao;
    @Autowired
    private BizCodeGeneratorFacade bizCodeGeneratorFacade;
    @Autowired
    private EduExamTestPaperDao eduExamTestPaperDao;
    @Autowired
    private EduTestPaperDao eduTestPaperDao;
    @Autowired
    private EduTestPaperService eduTestPaperService;
    @Autowired
    private EduExamCourseDao eduExamCourseDao;
    @Autowired
    private EduCourseCoursewareDao eduCourseCoursewareDao;
    @Autowired
    private EduCourseDao eduCourseDao;
    @Autowired
    private EduQuestionDao eduQuestionDao;
    @Autowired
    private EduQuestionItemDao eduQuestionItemDao;
    @Autowired
    private EduTestPaperQuestionDao eduTestPaperQuestionDao;
    @Autowired
    private EduExamUserScopeDao eduExamUserScopeDao;
    @Autowired
    private EduExamUserAnswerDao eduExamUserAnswerDao;
    @Autowired
    private CommonTypeService commonTypeService;
    @Autowired
    private EduExamRecordDao eduExamRecordDao;
    @Autowired
    private BaseUserDao baseUserDao;
    @Autowired
    private BpJobDao bpJobDao;
    @Autowired
    private BpDepartDao bpDepartDao;
    @Autowired
    private JPAQueryFactory queryFactory;
    @Autowired
    private EduCourseStatisticsService eduCourseStatisticsService;

    @Autowired
    private RedisClient redisClient;

    @Autowired
    private EduExamMapper eduExamMapper;

    @Autowired
    private EduTestPaperService testPaperService;

    @Autowired
    private AlertMessageService alertMessageService;
    @Autowired
    private BaseConfigService baseConfigService;
    @Autowired
    NoticeWaitingTriggerListService noticeWaitingTriggerListService;
    @Autowired
    private CommonIndexNotifyService commonIndexNotifyService;
    @Autowired
    private CommonBusinessNoticeService commonBusinessNoticeService;

    @Autowired
    private EduQuestionService questionService;

    @Autowired
    private BaseUserService baseUserService;

    @Autowired
    private EduCoursewareDao eduCoursewareDao;

    @Autowired
    private EduCourseLearnHistoryDao eduCourseLearnHistoryDao;

    @Value("${wwyt.image.upload.dir}")
    private String imageUploadDir;
    @Autowired
    private EduTrainningRecordMapper eduTrainningRecordMapper;
    @Autowired
    private BaseImageRepositoryService imageService;

    @Autowired
    private EduExamUserAnswerDao examUserAnswerDao;

    @Autowired
    private EduTrainningRecordDao trainningRecordDao;
    @Autowired
    private EduTestPaperQuestionService eduTestPaperQuestionService;
    @Autowired
    private EduExamRecordsMapper eduExamRecordsMapper;

    @Autowired
    private EduTrainningRecordUserMapper eduTrainningRecordUserMapper;
    @Autowired
    private BaseUserMapper baseUserMapper;
    @Autowired
    private EduAsyncService eduAsyncService;

    @Autowired
    private RedissonClient redissonClient;

    public static final String CACHE_EXAM_TIME = "CACHE_EXAM_TIME:%s:%s";

    public static final String SUBMIT_EXAM = "SUBMIT_EXAM:%s:%s";

    /**
     * 定义全局的常量值
     */
    private static final String XLS = "xls";
    /**
     * 定义全局的常量值
     */
    private static final String XLSX = "xlsx";


    @Override
    public EduExam add(EduExamEditBean entity) {
        EduExam content = new EduExam();
        String name = commonTypeService.getNameById(entity.getTypeId());
        if (StringUtils.isBlank(name)) {
            Assert.isTrue(false, "考试类型未找到");
        }
        String examName = entity.getName();
        Integer examByName = dao.selectByName(examName);
        if (examByName > 0) {
            Assert.isTrue(false, "当前考试名称已存在");
        }
        if (StringUtils.isNotBlank(entity.getCode())) {
            Assert.isTrue(dao.countOfCode(entity.getCode()) == 0, String.format("编号%s已经存在", entity.getCode()));
            content.setCode(entity.getCode());
        } else {
            content.setCode(bizCodeGeneratorFacade.kaoShi());
        }
        if (entity.getStartTime() != null && entity.getEndTime() != null) {
            Assert.isTrue(entity.getStartTime().before(entity.getEndTime()), "开始时间必须小于结束时间");
        }
        BeanUtils.copyProperties(entity, content, "id", "code");
        content.setState(ExamStates.INITIAL.getId());
        content.setJoinInCount(entity.getJoinInTargetIds() == null ? 0 : entity.getJoinInTargetIds().size());
        content.setExaminerUserIds(JSONUtil.toJsonStr(entity.getExaminerUserIds()));
        if (entity.getCreateUserName() == null) {
            content.setCreateUserName(baseUserDao.getBaseUserNameById(Context.getCurrentUserId()));
        }
        content.setCreateTime(entity.getCreateTime());
        dao.save(content);

        if (entity.getTypeId() != null) {
            commonTypeService.updateTypeCount(CommonTypes.EXAM, entity.getTypeId());
        }
        List<Long> userIds = findUserIds(entity.getJoinInOrgType(), entity.getJoinInTargetIds());
        //线上考试、关联培训考试
        if (content.getFormType() == 1 && content.getTrainType() == 1) {
            // 保存关联试卷
            saveExamTestPaper(entity.getTestPapers(),content.getId());
            eduAsyncService.saveRelatedTestPapers(content.getId(), entity.getTestPapers(), userIds, new CommonBean());
        }
        //线上考试、未关联培训考试
        if (content.getFormType() == ExamType.FORM_ONE.type && content.getTrainType() == ExamType.TRAIN_ZERO.type) {
            // 保存参与对象
            saveJoinInTargets(content.getId(), entity.getJoinInOrgType(), entity.getJoinInTargetIds());
            // 保存关联试卷
            saveExamTestPaper(entity.getTestPapers(),content.getId());
            eduAsyncService.saveRelatedTestPapers(content.getId(), entity.getTestPapers(), userIds, new CommonBean());
        }
        //线下考试、未关联培训考试
        if (content.getFormType() == ExamType.FORM_ZERO.type && content.getTrainType() == ExamType.TRAIN_ZERO.type) {
            eduTestPaperQuestionDao.deleteByExamId(content.getId());
            // 保存参与对象
            saveJoinInTargets(content.getId(), entity.getJoinInOrgType(), entity.getJoinInTargetIds());
        }
        // 保存关联课程
        //saveRelatedCourses(content.getId(), entity.getCourses());
        return content;
    }

    private List<Long> findUserIds(String orgType, List<Long> joinInTargetIds) {
        if ("J".equals(orgType) || "D".equals(orgType)) {
            joinInTargetIds = baseUserService.findUserIdsByTargetIds(orgType, joinInTargetIds);
        }
        return joinInTargetIds;

    }

    private void saveRelatedCourses(Long id, List<Long> courses) {
        if (courses == null || courses.isEmpty()) {
            return;
        }
        List<EduExamCourse> list = courses.stream().map(courseId -> new EduExamCourse(id, courseId, 0))
                .collect(Collectors.toList());
        eduExamCourseDao.saveAll(list);
    }

//    //保存关联试卷
//    @Async
//    public void saveRelatedTestPapers(Long id, List<Long> testPapers, List<Long> userIds) {
//        if (testPapers == null || testPapers.isEmpty()) {
//            return;
//        }
//        List<EduExamTestPaper> list = testPapers.stream().map(testPaperId -> new EduExamTestPaper(id, testPaperId, 0))
//                .collect(Collectors.toList());
//        eduExamTestPaperDao.saveAll(list);
//        EduTestPaper paper = testPaperService.findById(testPapers.get(0));
//
//        //如果是随机选题 每个人看到的不一样
//        if (StringUtils.isNotBlank(paper.getCreateMode()) && "random".equals(paper.getCreateMode())) {
//            if (org.apache.commons.collections.CollectionUtils.isNotEmpty(userIds)) {
//                List<EduTestPaperQuestion> result = Lists.newArrayList();
//                for (Long userId : userIds) {
//                    List<EduRandomSelectQuestionConfig> configs = JSONArray.parseArray(paper.getCreateConfig(), EduRandomSelectQuestionConfig.class);
//                    List<EduQuestionServiceImpl.QuestionSelectWrapper> wrappers = questionService.randomSelectWrapper(configs);
//                    List<EduTestPaperQuestion> questions = saveQuestions(paper.getId(), wrappers, userId, id);
//                    result.addAll(questions);
//                }
////                eduTestPaperQuestionDao.saveAll(result);
//                eduTestPaperQuestionService.saveBatch(result);
//            }
//
//        }
//    }

    // 保存试卷试题
    private void saveRelatedTestPaperQuestion(Long id, Long testPaperId, Long userId) {
        EduTestPaper paper = testPaperService.findById(testPaperId);
        if (StringUtils.isNotBlank(paper.getCreateMode()) && "random".equals(paper.getCreateMode())) {
            List<EduRandomSelectQuestionConfig> configs = JSONArray.parseArray(paper.getCreateConfig(), EduRandomSelectQuestionConfig.class);
            List<EduQuestionServiceImpl.QuestionSelectWrapper> wrappers = questionService.randomSelectWrapper(configs);
            List<EduTestPaperQuestion> eduTestPaperQuestions = saveQuestions(paper.getId(), wrappers, userId, id);
            eduTestPaperQuestionService.saveBatch(eduTestPaperQuestions);
        }
    }

    private List<EduTestPaperQuestion> saveQuestions(Long id, List<EduQuestionServiceImpl.QuestionSelectWrapper> questions, Long userId, Long examId) {
        if (questions == null || questions.isEmpty()) {
            return Lists.newArrayList();
        }
        Long tenantId = Context.getCurrentTenantId();
        String orgCode = Context.getCurrentOrgCode();
        Long currentUserId = Context.getCurrentUserId();
        List<EduTestPaperQuestion> list = new ArrayList<>(questions.size());
        int index = 0;
        // 如下修改逻辑允许同一题出现多次，如果不允许出现多次，需要按照questionid去重
        for (EduQuestionServiceImpl.QuestionSelectWrapper q : questions) {
            EduTestPaperQuestion question = new EduTestPaperQuestion();
            question.setQuestionId(q.q.getId());
            question.setScore(q.config.getScore().doubleValue());
            question.setTestPaperId(id);
            question.setUserId(userId);
            question.setExamId(examId);
            question.setIndexNo(index++);
            question.setTenentId(tenantId);
            question.setOrgCode(orgCode);
            question.setCreateBy(currentUserId);
            question.setCreateTime(new Date());
            question.setUpdateBy(currentUserId);
            question.setUpdateTime(new Date());
            question.setDeleteFlag("0");
            list.add(question);
        }
        return list;
//        eduTestPaperQuestionDao.saveAll(list);
    }

    private void saveJoinInTargets(Long id, String orgType, List<Long> joinInTargetIds) {
        if (joinInTargetIds == null || joinInTargetIds.isEmpty()) {
            return;
        }
        List<EduExamUserScope> list = joinInTargetIds.stream().map(objId -> new EduExamUserScope(id, orgType, objId))
                .collect(Collectors.toList());
        eduExamUserScopeDao.saveAll(list);
    }


    @Override
    public void update(EduExamEditBean entity) {
        EduExam content = dao.findById(entity.getId()).get();
        content.setOpenFace(entity.getOpenFace());
        Long newId = entity.getTypeId();
        Long oldId = content.getTypeId();
        if (content.getState() == ExamStates.PUBLISHED.getId() && content.getStartTime().before(new Date())) {
            List<EduExamTestPaper> list = eduExamTestPaperDao.findByExamId(entity.getId());
            if (content.getDurationMinutes() != null && !content.getDurationMinutes().equals(entity.getDurationMinutes())) {
                Assert.isTrue(content.getState() != ExamStates.PUBLISHED.getId(), "考试已开始，不能修改考试时长！");
            }
            if (!list.get(0).getTestPaperId().equals(entity.getTestPapers().get(0))) {
                Assert.isTrue(content.getState() != ExamStates.PUBLISHED.getId(), "考试开始，不可以再修改试卷信息");
            }
            List<EduExamUserScope> oldUserScope = eduExamUserScopeDao.findByExamId(entity.getId());
            List<Long> oldIds = oldUserScope.stream().map(EduExamUserScope::getObjId).collect(Collectors.toList());

            if (entity.getJoinInTargetIds().size() != oldIds.size()) {
                Assert.isTrue(content.getState() != ExamStates.PUBLISHED.getId(), "考试开始，不可以再修改人员信息");
            }
            if (!entity.getJoinInOrgType().equals(content.getJoinInOrgType())) {
                Assert.isTrue(content.getState() != ExamStates.PUBLISHED.getId(), "考试开始，不可以再修改人员信息");
            }
            boolean equalCollection = org.apache.commons.collections4.CollectionUtils.isEqualCollection(oldIds, entity.getJoinInTargetIds());
            if (!equalCollection) {
                Assert.isTrue(content.getState() != ExamStates.PUBLISHED.getId(), "考试开始，不可以再修改人员信息");
            }
        } else {
            eduExamTestPaperDao.deleteByExamId(content.getId());
            //关联培训不删除人员
            if (entity.getTrainType() == ExamType.TRAIN_ZERO.type){
                eduExamUserScopeDao.deleteByExamId(content.getId());
            }
//            eduTestPaperQuestionDao.deleteByExamId(content.getId());
            List<Long> userIds = findUserIds(entity.getJoinInOrgType(), entity.getJoinInTargetIds());
            //线上考试、关联培训考试
            if (entity.getFormType() == 1 && entity.getTrainType() == 1) {
                // 保存关联试卷
                userIds=eduExamMapper.selectUserByExamId(content.getId());
                saveExamTestPaper(entity.getTestPapers(),content.getId());
                eduAsyncService.saveRelatedTestPapers(content.getId(), entity.getTestPapers(), userIds, new CommonBean());
            }
            //线上考试、未关联培训考试
            if (entity.getFormType() == ExamType.FORM_ONE.type && entity.getTrainType() == ExamType.TRAIN_ZERO.type) {
                // 保存参与对象
                saveJoinInTargets(content.getId(), entity.getJoinInOrgType(), entity.getJoinInTargetIds());
                // 保存关联试卷
                saveExamTestPaper(entity.getTestPapers(),content.getId());
                eduAsyncService.saveRelatedTestPapers(content.getId(), entity.getTestPapers(), userIds, new CommonBean());
            }
            //线下考试、未关联培训考试
            if (entity.getFormType() == ExamType.FORM_ZERO.type && entity.getTrainType() == ExamType.TRAIN_ZERO.type) {
                // 保存参与对象
                eduTestPaperQuestionDao.deleteByExamId(content.getId());
                saveJoinInTargets(content.getId(), entity.getJoinInOrgType(), entity.getJoinInTargetIds());
            }
        }

//        Assert.isTrue(content.getState() != ExamStates.PUBLISHED.getId(), "考试已发布，不可以再修改");
        Assert.notNull(entity.getCode(), "编号不能为空");
        Assert.isTrue(dao.countOfCodeExcludeId(entity.getCode(), entity.getId()) == 0,
                String.format("编号%s已经存在", entity.getCode()));
        content.setCode(entity.getCode());
        if (entity.getStartTime() != null && entity.getEndTime() != null) {
            Assert.isTrue(entity.getStartTime().before(entity.getEndTime()), "开始时间必须小于结束时间");
        }
/*        if (!content.getDurationMinutes().equals(entity.getDurationMinutes())) {
            List<Long> userIds = findUserIds(entity.getJoinInOrgType(), entity.getJoinInTargetIds());
            for (Long userId : userIds) {
                String redisKey = String.format(CACHE_EXAM_TIME, entity.getId(), userId);
                String json = redisClient.get(redisKey);
                if (StringUtils.isNotBlank(json)) {
                    HashMap hashMap = JSONUtil.toBean(json, HashMap.class);
                    // 开始考试时间
                    String startTime = hashMap.get("startTime") + "";
                    // 新的考试结束时间
                    String newEndTime = DateUtil.formatDateTime(DateUtil.offsetMinute(DateUtil.parseDateTime(startTime), entity.getDurationMinutes()));
                    hashMap.put("endTime", newEndTime);
                    redisClient.setEx(redisKey, JSONUtil.toJsonStr(hashMap), content.getDurationMinutes(), TimeUnit.MINUTES);
                }
            }
        }*/
        BeanUtils.copyProperties(entity, content, "id", "code", "state");
        content.setExaminerUserIds(JSONUtil.toJsonStr(entity.getExaminerUserIds()));
        // 删除关联数据
        eduExamCourseDao.deleteByExamId(content.getId());


        // 保存关联课程
        //saveRelatedCourses(content.getId(), entity.getCourses());
        dao.save(content);

        if (!Objects.equal(newId, oldId)) {
            commonTypeService.updateTypeCount(CommonTypes.EXAM, newId);
            commonTypeService.updateTypeCount(CommonTypes.EXAM, oldId);
        }
    }

    private void saveExamTestPaper(List<Long> testPapers, Long id) {
        List<EduExamTestPaper> list = testPapers.stream().map(testPaperId ->
                {
                    EduExamTestPaper eduExamTestPaper = new EduExamTestPaper();
                    eduExamTestPaper.setExamId(id);
                    eduExamTestPaper.setTestPaperId(testPaperId);
                    eduExamTestPaper.setIndexNo(0);
                    eduExamTestPaper.setOrgCode(Context.getCurrentOrgCode());
                    eduExamTestPaper.setTenentId(Context.getCurrentTenantId());
                    return eduExamTestPaper;
                }
        ).collect(Collectors.toList());
        eduExamTestPaperDao.saveAll(list);
    }


    @Override
    public Page<EduExamListBean> findByPage(Pageable page, SearchEduExam condition) {
        Predicate predicate = prepareListSearchCondition(condition);

        Page<EduExam> data = predicate == null ? dao.findAll(page) : dao.findAll(predicate, page);
        return PageDataUtils.convertPageData(data, new PageDataConvert<EduExamListBean, EduExam>() {

            @Override
            public EduExamListBean convert(EduExam f) {
                EduExamListBean bean = new EduExamListBean();
                BeanUtils.copyProperties(f, bean);
                bean.setTypeName(commonTypeService.getNameById(f.getTypeId()));
                bean.setStateName(ExamStates.findNameById(f.getState()));
                EduTrainningRecord trainningRecord = trainningRecordDao.selectExamId(f.getId());
                if (trainningRecord != null) {
                    bean.setStartTime(trainningRecord.getTrainningDate());
                    bean.setEndTime(trainningRecord.getTrainningEndDate());
                }
                return bean;
            }

        });
    }

    private Predicate prepareListSearchCondition(SearchEduExam condition) {
        Predicate p = null;
        QEduExam q = QEduExam.eduExam;
        if (condition.getNameOrCodeOrCreateUser() != null) {
            String s = condition.getNameOrCodeOrCreateUser();
            p = ExpressionUtils.or(q.name.contains(s),
                    ExpressionUtils.or(q.code.contains(s), q.createUserName.contains(s)));
        }
        if (condition.getTypeId() != null) {
            p = ExpressionUtils.and(p, q.typeId.eq(condition.getTypeId()));
        }
        return p;
    }

    @Override
    public void delete(Long id) throws BusinessRestrictionsException {

        // 校验业务逻辑是否允许删除。如本数据被其他业务关联，删除后导致其他业务不可用。不能删除时将抛出业务异常
        checkBussinessRestrictions(id);
        // 删除本次考试下所有试卷的关联关系
        eduExamTestPaperDao.deleteByExamId(id);
        EduExam one = dao.getOne(id);
        dao.deleteById(id);
        commonTypeService.updateTypeCount(CommonTypes.EXAM, one.getTypeId());
    }

    /**
     * 校验业务逻辑是否允许删除。如本数据被其他业务关联，删除后导致其他业务不可用
     *
     * @param id 要删除的业务ID
     */
    private void checkBussinessRestrictions(Long id) throws BusinessRestrictionsException {
        dao.findById(id)
                .ifPresent(exam -> Assert.isTrue(exam.getState() != ExamStates.PUBLISHED.getId(), "已发布的考试不可以删除"));
        EduTrainningRecord trainningRecord = trainningRecordDao.selectExamId(id);
        if (trainningRecord != null) {
            Assert.isTrue(false, "该考试已被培训关联，无法删除");
        }

    }

    @Override
    public EduExam findById(Long id) {
        return dao.findById(id).orElse(null);
    }


    @Override
    public EduExamDetailBean getById(Long id,Boolean isWeb) {
        EduExam entity = findById(id);
        if (entity == null) {
            return null;
        }
        EduExamDetailBean bean = new EduExamDetailBean();
        EduDoExamResultBean examResultBean = new EduDoExamResultBean();
        examResultBean.setId(id);
        String cacheAnswers = cacheAnswers(examResultBean, Context.getCurrentUserId(), 3);
        if (StringUtils.isNotBlank(cacheAnswers)) {
            EduDoExamResultBean eduDoExamResultBean = JSONUtil.toBean(cacheAnswers, EduDoExamResultBean.class);
            // 系统自动提交
            if (eduDoExamResultBean.getIsSubmit().equals(3)) {
                bean.setIsSubmit(3);
                bean.setExamResult(eduDoExamResultBean.getExamResult());
                delAllCacheAnswers(id, Context.getCurrentUserId());
//                throw new IllegalArgumentException("考试已到时,系统已自动提交");
            }
        }
        BeanUtils.copyProperties(entity, bean);
        bean.setTypeName(commonTypeService.getNameById(entity.getTypeId()));
      //  bean.setCourses(queryCoursesOfExam(id));
        bean.setTestPapers(queryTestPapersOfExam(id));
        if (isWeb){
            bean.setJoinUsers(queryUsersOfExam(entity.getJoinInOrgType(), id));
        }
        if (entity.getFormType() == ExamType.FORM_ONE.type) {
//            bean.setIsSupervisorQuestion(checkExamContainZgt(id) ? 1 : 0);
            if (!CollectionUtils.isEmpty(bean.getTestPapers())){
                bean.setIsSupervisorQuestion(bean.getTestPapers().get(0).getIsSupervisorQuestion());
            }
        }
        List<Long> examinerUserIds = JSONUtil.toList(entity.getExaminerUserIds(), Long.class);
        bean.setExaminerUserIds(examinerUserIds);
        bean.setExaminerUsers(CollectionUtil.isEmpty(examinerUserIds) ? ListUtil.empty() : queryExaminerUser(examinerUserIds));
        Date now = new Date();
        boolean canJoin = isExamInEffectiveTimeRange(entity);
        String stateName = "";
        if (!canJoin) {
            // 不能参与的考试，只有2种情况，一种是未开始，一种是已过期
            if (entity.getStartTime() != null && entity.getStartTime().after(now)) {
                stateName = TakeExamStates.NOT_STARTED.getName();
            }
            if (entity.getEndTime() != null && entity.getEndTime().before(now)) {
                stateName = TakeExamStates.OUT_OF_DATE.getName();
            }
        } else {
            stateName = TakeExamStates.INPROGRESSING.getName();
        }
        bean.setStateName(stateName);
        List<EduTrainningRecord> eduTrainningRecords = eduTrainningRecordMapper.selectByExamId(id);
        if (!CollectionUtils.isEmpty(eduTrainningRecords)) {
            bean.setDeleteType(1);
        } else {
            bean.setDeleteType(0);
        }
        return bean;
    }

    private boolean resitNeedRandom(EduExam exam, Long userId) {
        boolean isTryAgain = false;
        boolean canJoin = isExamInEffectiveTimeRange(exam);
        // 查询考试时间缓存,若存在, 则说明已经生成过的试题还未提交, 不需要重新生成试题。
        String examTime = cacheExamTime(exam.getId(), userId, 3);
        if (canJoin && StringUtils.isEmpty(examTime)) {
            EduExamRecord record = eduExamRecordDao.selectByUserIdAndExamIdLatest(userId, exam.getId());
            if (record != null) {
                boolean passed = record.getState() != null && record.getState() == 1;
                // 设置为未通过可重考时，本次提交的答案未通过 且 （第一次提交 或 提交次数小于限制次数 或 不限制次数）
                if (Constant.ALLOW_TRY_AGAIN.equals(exam.getAllowTryAgain()) && !passed
                        && (record.getSubmitTimes() == 0 || record.getSubmitTimes() < exam.getTryAgainLimit() + 1 || Constant.TRY_AGAIN_LIMIT_OFF.equals(exam.getTryAgainLimitFlag()))) {
                    isTryAgain = true;
                }
                // 设置为随时可重考
                if (Constant.ANY_TIME_ALLOW_TRY_AGAIN.equals(exam.getAllowTryAgain())) {
                    isTryAgain = true;
                }
            }
        }
        return StringUtils.isEmpty(examTime) && isTryAgain && TakeExamStates.TO_BE_MARKED.getId() != exam.getState() && Constant.COMMON_SWITCH_ON.equals(String.valueOf(exam.getRandomizeQuestionResit()));
    }

    public List<EduExaminerUserDetailBean> queryExaminerUser(List<Long> examinerUserIds) {
        List<EduExaminerUserDetailBean> list = new ArrayList<>();
        List<BpBaseUserBean> baseUserList = baseUserMapper.findUserNamesByUserIds(examinerUserIds);
//        List<BaseUser> baseUserList = baseUserDao.getUsersByIds(examinerUserIds);
        for (BpBaseUserBean baseUser : baseUserList) {
            EduExaminerUserDetailBean eduExaminerUserDetailBean = new EduExaminerUserDetailBean();
            eduExaminerUserDetailBean.setId(baseUser.getId());
            eduExaminerUserDetailBean.setName(baseUser.getName());
            eduExaminerUserDetailBean.setType(ExamJoinInTargetTypes.USER.getCode());
            list.add(eduExaminerUserDetailBean);
        }
        return list;
    }

    public List<EduExamUserScopeDetailBean> queryUsersOfExam(String orgType, Long id) {
        ExamJoinInTargetTypes type = ExamJoinInTargetTypes.findByCode(orgType);
        if (type == ExamJoinInTargetTypes.USER) {
            // 用户
            return joinInExamUsers(id);
        } else if (type == ExamJoinInTargetTypes.JOB) {
            // 岗位参与
            return joinInExamJobs(id);
        } else if (type == ExamJoinInTargetTypes.DEPART) {
            // 部门参与
            return joinInExamDeparts(id);
        }
        return null;
    }

    // 查询参与的部门信息
    private List<EduExamUserScopeDetailBean> joinInExamDeparts(Long id) {
        List<EduExamUserScope> scopes = eduExamUserScopeDao.findByExamId(id);
        List<Long> ids = scopes.stream().map(EduExamUserScope::getObjId).collect(Collectors.toList());
        return bpDepartDao.findAllById(ids).stream().map(this::convertDepartToUserScope).collect(Collectors.toList());
    }

    private EduExamUserScopeDetailBean convertDepartToUserScope(BpDepart depart) {
        EduExamUserScopeDetailBean bean = new EduExamUserScopeDetailBean();
        bean.setType(ExamJoinInTargetTypes.DEPART.getCode());
        bean.setId(depart.getId());
        bean.setDepartName(depart.getName());
        return bean;
    }

    // 查询参与的岗位信息
    private List<EduExamUserScopeDetailBean> joinInExamJobs(Long id) {
        List<EduExamUserScope> scopes = eduExamUserScopeDao.findByExamId(id);
        List<Long> ids = scopes.stream().map(EduExamUserScope::getObjId).collect(Collectors.toList());
        return bpJobDao.findAllById(ids).stream().map(this::convertJobToUserScope).collect(Collectors.toList());
    }

    private EduExamUserScopeDetailBean convertJobToUserScope(BpJob job) {
        EduExamUserScopeDetailBean bean = new EduExamUserScopeDetailBean();
        bean.setType(ExamJoinInTargetTypes.JOB.getCode());
        bean.setId(job.getId());
        bean.setJobName(job.getName());
        return bean;
    }

    // 查询参与的人员信息
    private List<EduExamUserScopeDetailBean> joinInExamUsers(Long id) {
//        List<EduExamUserScope> scopes = eduExamUserScopeDao.findByExamId(id);
//        List<Long> ids = scopes.stream().map(EduExamUserScope::getObjId).collect(Collectors.toList());
//        return baseUserDao.findAllById(ids).stream().map(this::convertUserToUserScope).collect(Collectors.toList());
        return eduExamMapper.selectExamPeople(id);
    }

    private EduExamUserScopeDetailBean convertUserToUserScope(BaseUser job) {
        EduExamUserScopeDetailBean bean = new EduExamUserScopeDetailBean();
        BpJob one = bpJobDao.getOne(job.getJob());
        BpDepart bpDepart = bpDepartDao.getOne(job.getDepartId());
        bean.setType(ExamJoinInTargetTypes.USER.getCode());
        bean.setId(job.getId());
        bean.setRealName(job.getRealName());
        bean.setJob(job.getJob());
        bean.setJobName(one.getName());
        bean.setCode(job.getCode());
        bean.setDepartId(job.getDepartId());
        bean.setDepartName(bpDepart.getName());
        return bean;
    }

    public List<EduExamTestPaperDetailBean> queryTestPapersOfExam(Long id) {
        List<EduExamTestPaper> list = eduExamTestPaperDao.findByExamId(id);
        if (list.isEmpty()) {
            return Collections.EMPTY_LIST;
        }
        List<Long> ids = list.stream().map(EduExamTestPaper::getTestPaperId).collect(Collectors.toList());
        return eduTestPaperDao.findAllById(ids).stream().map(this::convertToTestPaperDetail)
                .collect(Collectors.toList());
    }

    private EduExamTestPaperDetailBean convertToTestPaperDetail(EduTestPaper paper) {
        EduExamTestPaperDetailBean bean = new EduExamTestPaperDetailBean();
        bean.setId(paper.getId());
        bean.setTypeName(commonTypeService.getNameById(paper.getTypeId()));
        bean.setName(paper.getName());
        bean.setQuestionCount(paper.getQuestionCount());
        bean.setTotalScore(paper.getTotalScore());
        bean.setCreateMode(paper.getCreateMode());
        bean.setIsSupervisorQuestion(paper.getIsSupervisorQuestion());
        return bean;
    }

    private List<EduExamCourseDetailBean> queryCoursesOfExam(Long id) {
        List<EduExamCourse> list = eduExamCourseDao.findByExamId(id);
        if (list.isEmpty()) {
            return Collections.EMPTY_LIST;
        }
        List<Long> ids = list.stream().map(EduExamCourse::getCourseId).collect(Collectors.toList());
        return eduCourseDao.findAllById(ids).stream().map(this::convertToCourseDetail).collect(Collectors.toList());
    }

    // 转换为问卷明细
    private EduExamCourseDetailBean convertToCourseDetail(EduCourse paper) {
        EduExamCourseDetailBean bean = new EduExamCourseDetailBean();
        bean.setId(paper.getId());
        bean.setName(paper.getName());
        // bean.setTypeId(paper);
        return bean;
    }

    @Override
    public void changeState(Long id, Integer state) throws ApiBusinessException {
        EduExam one = dao.getOne(id);
        if (one.getState() == state) {
            return;
        }
        if (one.getState() == ExamStates.PUBLISHED.getId()) {
            //Assert.isTrue(one.getStartTime() != null && one.getStartTime().before(new Date()), "已经开始的考试不可以取消发布");
            List<EduExamRecord> eduExamRecords = eduExamRecordDao.getExamId(one.getId());
            for (EduExamRecord eduExamRecord : eduExamRecords) {
                eduExamRecord.setDeleteFlag("1");
                eduExamRecordDao.save(eduExamRecord);
            }
        }
        if (state==ExamStates.INITIAL.getId()){
            eduAsyncService.deleteRedisKeyByExamId(id);
        }

        one.setReleaseTime(new Date());
        one.setState(state);
        dao.save(one);
        // 保存将要推送的消息
//        saveToTrigger(one.getId(), one.getStartTime(), one.getCode(),one.getState());
        eduAsyncService.saveToTriggerExam(one.getId(), one.getStartTime(), one.getCode(), one.getState(), new CommonBean());
    }

//    /**
//     * 考试通知存入待触发表
//     * APP端直接推送,WEB存入通知表后,被扫描到再推送
//     */
//    @Async("formDataStatisticAsync")
//    public void saveToTrigger(Long examId, Date startTime, String taskCode,Integer state) throws ApiBusinessException {
//        //BaseConfig examConfig = baseConfigService.findFirstConfig(BaseConfigCodes.GETUI_EXAM_PUBLISH_NOTIFICATION_CONTENT);
//        Set<Long> userIds = getUsersIds(examId);
//        BaseConfig baseConfig = new BaseConfig();
//        EduExam eduExam = eduExamMapper.selectById(examId);
//                BaseConfig notificationTitle = baseConfigService.findFirstConfig(BaseConfigCodes.GETUI_EXAM_NOTIFICATION_TITLE);
//        if (notificationTitle == null) {
//            throw new ApiBusinessException(Status.NOTICE_NOT_CONFIGURATION_PARAM);
//        }
//        String msgTitle = notificationTitle.getValue();
//        if (state == 1){
//        baseConfig = baseConfigService.findFirstConfig(BaseConfigCodes.GETUI_EXAM_PUBLISH_NOTIFICATION_CONTENT);
//        String subMsgBody = baseConfig.getValue();
//        String msgBody = String.format(subMsgBody, TrainingFrom.findNameById(Math.toIntExact(eduExam.getFormType())),eduExam.getName(),SimpleDateFormatCache.getYmdhm().format(eduExam.getStartTime()));
//        alertMessageService.pushSingleBatchCid(userIds, msgTitle, msgBody, null, true);
//        //向web端发送通知
//        commonIndexNotifyService.releaseExamReminder(userIds, examId, msgTitle, startTime, msgBody);
//        // app通知
//        generateAppNotify(userIds, msgBody, examId);
//        }else {
//        baseConfig = baseConfigService.findFirstConfig(BaseConfigCodes.GETUI_EXAM_CANCEL_NOTIFICATION_CONTENT);
//        String subMsgBody = baseConfig.getValue();
//        String msgBody = String.format(subMsgBody, eduExam.getName());
//        alertMessageService.pushSingleBatchCid(userIds, msgTitle, msgBody, null, true);
//        //向web端发送通知
//        commonIndexNotifyService.releaseExamReminder(userIds, examId, msgTitle, startTime, msgBody);
//        // app通知
//        generateAppNotify(userIds, msgBody, examId);
//        }
//        if (baseConfig == null) {
//            throw new ApiBusinessException(Status.NOTICE_NOT_CONFIGURATION_PARAM);
//        }
//    }
//
//    private void generateAppNotify(Set<Long> userIds, String msgBody, Long examId) {
//        // app通知中心
//        ArrayList<CommonBusinessNotice> commonBusinessNotices = new ArrayList<>();
//        for (Long userId : userIds) {
//            String msg = MessageFormat.format(msgBody, baseUserDao.getRealNameById(userId));
//            CommonBusinessNotice appNotify = new CommonBusinessNotice();
//            appNotify.setBusinessType(AppBusinessNoticeTypes.EDU.getType())
//                    .setNoticeContent(msg)
//                    .setNoticeUser(userId)
//                    .setNoticeTitle(AppNotifyContentPre.EDU_NOTIFY)
//                    .setSendTime(new Date())
//                    .setRefId(examId);
//            commonBusinessNotices.add(appNotify);
//        }
//        commonBusinessNoticeService.saveBatch(commonBusinessNotices);
//    }
//
//    /**
//     * 根据试卷信息获取所有参与者
//     *
//     * @param examId
//     * @return alias
//     */
//    public Set<Long> getUsersIds(Long examId) {
//        Set<Long> userIds = new HashSet<>();
//        List<EduExamUserScope> scopes = eduExamUserScopeDao.findByExamId(examId);
//        for (EduExamUserScope scope : scopes) {
//            if (ExamJoinInTargetTypes.USER.getCode().equals(scope.getObjType())) {
//                userIds.add(scope.getObjId());
//                continue;
//            }
//            if (ExamJoinInTargetTypes.JOB.getCode().equals(scope.getObjType())) {
//                List<Long> ids = baseUserDao.findIdByJobAndCompany(scope.getObjId());
//                userIds.addAll(ids);
//                continue;
//            }
//            if (ExamJoinInTargetTypes.DEPART.getCode().equals(scope.getObjType())) {
//                List<Long> ids = baseUserDao.findByDepartIdAndCompany(scope.getObjId());
//                userIds.addAll(ids);
//            }
//        }
//        return userIds;
//    }

    @Override
    public Page<EduExamCenterListBean> queryUserExamCenterPage(Pageable page, Long userId,
                                                               SearchEduExamCenter condition) {
        BaseUser user = baseUserDao.getOne(userId);
        Assert.notNull(user, "用户#" + userId + "不存在");
        Long departId = user.getDepartId();
        Long jobId = user.getJob();
        List<Long> examIds = eduExamUserScopeDao.findUserCanJoinExams(userId, departId, jobId);
        if (examIds == null) {
            return new PageImpl<>(Collections.EMPTY_LIST);
        }
        Predicate p = createExamCenterCondition(userId, examIds, condition);
        page = PageDataUtils.addOrderSort(page, "DESC", "startTime", "endTime");
        Page<EduExam> all = dao.findAll(p, page);
        if (all == null) {
            return new PageImpl<>(Collections.EMPTY_LIST);
        }
        Page<EduExamCenterListBean> eduExamCenterListBeans = convertExamRecords(userId, all);
        List<EduExamCenterListBean> content = eduExamCenterListBeans.getContent();
        List<EduExamCenterListBean> sortCollect = content.stream().sorted(Comparator.comparing(EduExamCenterListBean::getStartTime).thenComparing(EduExamCenterListBean::getEndTime).reversed()).collect(Collectors.toList());
        return new PageImpl<>(sortCollect, eduExamCenterListBeans.getPageable(), eduExamCenterListBeans.getTotalElements());

    }


    @Override
    public ResultBean queryUserMarkingPapersCenterPage(Pageable page, Long userId, EduMarkingPapersCenterListBean.SearchEduMarkingPapersCenter params) {

        List<EduExamRecord> records = eduExamRecordDao.selectToBeMarkedList();
        List<EduMarkingPapersCenterListBean> list = new ArrayList<>();
        for (EduExamRecord record : records) {

            EduExam eduExam = dao.getOne(record.getExamId());

            if (StrUtil.isEmpty(eduExam.getExaminerUserIds()) || (StrUtil.isNotBlank(params.getExamName()) && !eduExam.getName().contains(params.getExamName()))) {
                continue;
            }

            List<Long> examinerUserIds = JSONUtil.toList(eduExam.getExaminerUserIds(), Long.class);
            // 判断当前用户是否有 阅卷权限
            boolean present = examinerUserIds.stream().anyMatch(v -> v.equals(userId));
            if (!present) {
                continue;
            }

            BpUserBean userBean = baseUserService.selectByUserId(record.getUserId());
            if (StrUtil.isNotBlank(params.getUserName()) && !userBean.getRealName().contains(params.getUserName())) {
                continue;
            }
            if (params.getJobId() != null && !userBean.getJobId().equals(params.getJobId())) {
                continue;
            }
            if (params.getDepartId() != null && !userBean.getDepartId().equals(params.getDepartId())) {
                continue;
            }
            EduMarkingPapersCenterListBean bean = new EduMarkingPapersCenterListBean();

            bean.setExamId(eduExam.getId());

            bean.setRecordId(record.getId());
            // 用户名称
            bean.setUserName(userBean.getRealName());
            //部门名称
            bean.setDepartName(userBean.getUserDepartName());
            // 岗位名称
            bean.setJobName(userBean.getUserJobName());
            // 考试名称
            bean.setExamName(eduExam.getName());
            // 考试类型
            bean.setExamTypeName(commonTypeService.getNameById(eduExam.getTypeId()));
            // 考试提交时间
            bean.setSubmitTime(record.getSubmitTime());
            // 是否阅卷
            bean.setIsCheck(record.getIsCheck());
            list.add(bean);
        }
        List<EduMarkingPapersCenterListBean> sortList =
                list.stream().sorted(Comparator.comparing(EduMarkingPapersCenterListBean::getSubmitTime).reversed()).collect(Collectors.toList());

        List<EduMarkingPapersCenterListBean> pageList = ListUtil.page(page.getPageNumber(), page.getPageSize(), sortList);
        return ResultBean.pageData(pageList, sortList.size());
    }

    @Override
    public String checkCourseLearn(Long userId, Long examId) {
        String name = dao.getNameById(examId);
        Assert.isTrue(StrUtil.isNotBlank(name), "考试不存在，或已被删除");

        List<EduTrainningRecord> eduTrainningRecords = eduTrainningRecordMapper.selectByExamId(examId);
        List<EduTrainningRecord> filteredRecords = eduTrainningRecords.stream()
                .filter(trainningRecord -> !ObjectUtils.isEmpty(trainningRecord.getTrainningEndDate()))
                .collect(Collectors.toList());

        if (!CollectionUtils.isEmpty(filteredRecords)) {
            filteredRecords.forEach(trainningRecord -> {
                if (!ObjectUtils.isEmpty(trainningRecord)) {
                    if (!isFinishCourse(userId, trainningRecord.getId())) {
                        throw new IllegalArgumentException("未学完课程，不允许考试");
                    }
                }
            });
        }

        // 已学习的课件ids
        List<Long> coursewareLearnedIds = eduCourseStatisticsService.selectCoursewareLearnedByUserId(userId);
        // 查询考试下的所有课程
        List<EduExamCourse> examCourseList = eduExamCourseDao.findByExamId(examId);

        StringBuilder sb = new StringBuilder();
        for (EduExamCourse eduExamCourse : examCourseList) {

            if (CollectionUtil.isNotEmpty(coursewareLearnedIds)) {
                // 课件ids
                List<Long> coursewareIds = eduCourseCoursewareDao.findCoursewareIdsByCourseId(eduExamCourse.getCourseId());
                filterData(coursewareLearnedIds, userId);
                // 判断课程下的课件是否学习完
                if (!coursewareLearnedIds.containsAll(coursewareIds)) {

                    sb.append(eduCourseDao.getNameById(eduExamCourse.getCourseId()));
                    sb.append(",");
                }
            } else {
                sb.append(eduCourseDao.getNameById(eduExamCourse.getCourseId()));
                sb.append(",");
            }

        }
        return StrUtil.isNotBlank(sb) ? sb.substring(0, sb.length() - 1) : "";
    }

    public void filterData(List<Long> coursewareIds, Long userId) {
        //视频课件集合
        List<Long> videoIds = eduCoursewareDao.findVideo(coursewareIds);
        if (org.apache.commons.collections4.CollectionUtils.isNotEmpty(videoIds)) {
            coursewareIds.removeAll(videoIds);
            for (Long videoId : videoIds) {
                Integer num = eduCourseLearnHistoryDao.findByIdsAndState(videoId, userId);
                if (num != null && num > 0) {
                    coursewareIds.add(videoId);
                }
            }
        }
    }

    /**
     * @param userId
     * @param all
     * @return
     */
    protected Page<EduExamCenterListBean> convertExamRecords(Long userId, Page<EduExam> all) {
        Map<Long, String> typeMap = new HashMap<>();
        if (all != null && !CollectionUtils.isEmpty(all.getContent())) {
            List<Long> typeIds = all.getContent().stream().map(EduExam::getTypeId).collect(Collectors.toList());
            if (!CollectionUtils.isEmpty(typeIds)) {
                List<CommonType> list = commonTypeService.getNameByIds(typeIds);
                typeMap = list.stream().collect(Collectors.toMap(CommonType::getId, CommonType::getName));
            }
        }

        Map<Long, String> finalTypeMap = typeMap;
        return PageDataUtils.convertPageData(all, new PageDataConvert<EduExamCenterListBean, EduExam>() {

            @Override
            public EduExamCenterListBean convert(EduExam exam) {
                EduExamCenterListBean convertResult = new EduExamCenterListBean();
                BeanUtils.copyProperties(exam, convertResult);
                convertResult.setFormTypeName(TrainingFrom.findNameById(Math.toIntExact(exam.getFormType())));
                if (exam.getTrainType() == 1) {
                    //非临时性考试已关联培训 以培训时间为准
/*                    EduTrainningRecord record = eduTrainningRecordMapper.selectByExamId(exam.getId());
                    if (record != null) {
                        convertResult.setStartTime(record.getTrainningDate());
                        convertResult.setEndTime(record.getTrainningEndDate() == null ? null : record.getTrainningEndDate());
                    }*/
                }

                convertResult.setQuestionCount(dao.countOfQuestion(exam.getId()));
//                convertResult.setTypeName(commonTypeService.getNameById(exam.getTypeId()));
                convertResult.setTypeName(finalTypeMap.get(exam.getTypeId()));
                // 设置总分、及格分
                setTotalScoreAndPassScore(exam.getId(), convertResult);
                EduExamRecord record = eduExamRecordDao.selectByUserIdAndExamIdLatest(userId, exam.getId());
                //查询记录包含已删除的记录
//                EduExamRecord record = eduExamRecordDao.selectAllEduExamRecordsByUserIdAndExamIdLatest(userId, exam.getId());
//                if (record!=null&&"1".equals(record.getDeleteFlag())){
//                    return null;
//                }
                convertResult.setHasRecord(record != null);

                if (record == null) {
                    int state = 0;
                    String stateName = null;
                    Date now = new Date();
                    boolean canJoin = isExamInEffectiveTimeRange(exam);
                    convertResult.setCanDo(canJoin);
                    if (exam.getShowAutograph() != null && 1 == exam.getShowAutograph()) {
                        convertResult.setCanDo(false);
                    }
                    if (!canJoin) {
                        // 不能参与的考试，只有2种情况，一种是未开始，一种是已过期
                        if (exam.getStartTime() != null && exam.getStartTime().compareTo(now) > 0) {
                            state = TakeExamStates.NOT_STARTED.getId();
                            stateName = TakeExamStates.NOT_STARTED.getName();
                        }
                        if (exam.getEndTime() != null && exam.getEndTime().compareTo(now) < 0) {
                            state = TakeExamStates.OUT_OF_DATE.getId();
                            stateName = TakeExamStates.OUT_OF_DATE.getName();
                        }

                    } else {
                        state = TakeExamStates.INPROGRESSING.getId();
                        stateName = TakeExamStates.INPROGRESSING.getName();
                    }
                    convertResult.setState(state);
                    convertResult.setStateName(stateName);
                } else {
                    boolean canJoin = isExamInEffectiveTimeRange(exam);
                    boolean passed = false;
                    if (record.getState() != null && record.getState() == 1) {
                        passed = true;
                    }
                    convertResult.setCanDo(false);
                    convertResult.setExamRecordId(record.getId());
                    convertResult.setLastDoneTime(record.getSubmitTime());
                    convertResult.setScore(record.getScore());
                    convertResult.setPassed(passed);
                    convertResult.setState(passed ? TakeExamStates.PASSED.getId() : TakeExamStates.NOT_PASSED.getId());
                    convertResult.setStateName(passed ? "考试通过" : "考试未通过");
                    if (record.getScore().equals("未考")) {
                        convertResult.setScore("");
                        convertResult.setStateName("未考");
                    }
                    if (record.getIsCheck() == 1) {
                        convertResult.setStateName("等待阅卷");
                        convertResult.setState(TakeExamStates.TO_BE_MARKED.getId());
                    }
                    // 必须在限制时间内
                    if (canJoin) {
                        // 设置为未通过可重考时，本次提交的答案未通过 且 （第一次提交 或 提交次数小于限制次数 或 不限制次数）
                        if (Constant.ALLOW_TRY_AGAIN.equals(exam.getAllowTryAgain()) && !passed
                                && (record.getSubmitTimes() == 0 || record.getSubmitTimes() < exam.getTryAgainLimit() + 1 || Constant.TRY_AGAIN_LIMIT_OFF.equals(exam.getTryAgainLimitFlag()))) {
                            convertResult.setCanTryAgain(true);
                        }
                        // 设置为随时可重考
                        if (Constant.ANY_TIME_ALLOW_TRY_AGAIN.equals(exam.getAllowTryAgain())) {
                            convertResult.setCanTryAgain(true);
                        }
                    }
                }
                // bean.set
                return convertResult;
            }

        });
    }

    /**
     * 考试是否在有效期间内
     *
     * @param f
     * @return
     */
    private boolean isExamInEffectiveTimeRange(EduExam f) {
        boolean canJoin = false;
        Date now = new Date();
        if (f.getStartTime() != null && f.getEndTime() != null) {
            canJoin = now.after(f.getStartTime()) && now.before(f.getEndTime());
        } else if (f.getStartTime() != null) {
            canJoin = now.after(f.getStartTime());
        } else if (f.getEndTime() != null) {
            canJoin = now.before(f.getEndTime());
        } else {
            // 没有设置，则表示随时可以做
            canJoin = true;
        }
        return canJoin;
    }

    protected void setTotalScoreAndPassScore(Long id, EduExamCenterListBean bean) {
        EduExam one = dao.getOne(id);
        if (one.getFormType() == 0) {
            bean.setTotalScore(one.getTotalScore());
            bean.setPassScore(one.getPassScore());
        } else {
            Tuple tuple = dao.sumTotalScoreAndPassScore(id);
            if (tuple != null) {
                Number totalScore = tuple.get("totalScore", Number.class);
                Number passScore = tuple.get("passScore", Number.class);
                bean.setTotalScore(totalScore != null ? totalScore.floatValue() : 0);
                bean.setPassScore(passScore != null ? passScore.floatValue() : 0);
            }
        }
    }

    private Predicate createExamCenterCondition(Long userId, List<Long> examIds, SearchEduExamCenter condition) {
        QEduExam q = QEduExam.eduExam;
        // 只能查看已发布的问卷
        Predicate p = ExpressionUtils.and(q.id.in(examIds), q.state.eq(ExamStates.PUBLISHED.getId()));
        if (condition.getNameOrCode() != null) {
            p = ExpressionUtils.and(p, ExpressionUtils.or(q.name.contains(condition.getNameOrCode()),
                    q.code.contains(condition.getNameOrCode())));
        }
        if (condition.getName() != null) {
            p = ExpressionUtils.and(p, q.name.contains(condition.getName()));
        }
        if (condition.getTypeId() != null) {
            p = ExpressionUtils.and(p, q.typeId.eq(condition.getTypeId()));
        }
        if (condition.getEndTime() != null) {
            p = ExpressionUtils.and(p, q.endTime.loe(condition.getEndTime()));
        }
        if (condition.getState() != null) {
            // APP端 搜索已考试，未考试
            List<EduExamRecord> records = eduExamRecordDao.selectByUserIdLatest(userId);
            System.err.println(condition.getState());
            Set<Long> recordExamIds = records.stream().map(EduExamRecord::getExamId).collect(Collectors.toSet());
            if ("finished".equals(condition.getState())) {
                // app端，finished表示已经考过或者已经过期的
                p = ExpressionUtils.and(p, q.id.in(recordExamIds).or(q.endTime.lt(new Date())));
            } else {
                p = ExpressionUtils.and(p, q.id.notIn(recordExamIds));
            }
            // app查询进行中或未开始的考试
            if ("undo".equals(condition.getState())) {
                p = ExpressionUtils.and(p, q.state.eq(1).and(q.endTime.gt(new Date())));
            }
        }
        if (condition.getPcState() != null) {
            List<EduExamRecord> records = eduExamRecordDao.selectByUserIdLatest(userId);
            // 未开始
            if (condition.getPcState() == 6) {
                Set<Long> recordExamIds = records
                        .stream()
                        .map(EduExamRecord::getExamId)
                        .collect(Collectors.toSet());
                p = ExpressionUtils.and(p, q.id.notIn(recordExamIds));
                p = ExpressionUtils.and(p, q.startTime.gt(new Date()));
            }
            // 进行中
            if (condition.getPcState() == 1) {
                Set<Long> recordExamIds = records
                        .stream()
                        .map(EduExamRecord::getExamId)
                        .collect(Collectors.toSet());
                p = ExpressionUtils.and(p, q.id.notIn(recordExamIds));
                p = ExpressionUtils.and(p, q.startTime.lt(new Date()).and(q.endTime.gt(new Date())));
            }
            // 过期
            if (condition.getPcState() == 2) {
                Set<Long> recordExamIds = records
                        .stream()
                        .map(EduExamRecord::getExamId)
                        .collect(Collectors.toSet());
                p = ExpressionUtils.and(p, q.id.notIn(recordExamIds));
                p = ExpressionUtils.and(p, q.endTime.lt(new Date()));
            }
            // 3 未通过
            if (condition.getPcState() == 3) {
                Set<Long> recordExamIds = records.stream().filter(v -> v.getState() == 0 && (v.getIsCheck() == 2 || v.getIsCheck() == 0)).map(EduExamRecord::getExamId).collect(Collectors.toSet());
                p = ExpressionUtils.and(p, q.id.in(recordExamIds));
            }
            // 4 已通过0
            if (condition.getPcState() == 4) {
                Set<Long> recordExamIds = records.stream().filter(v -> v.getState() == 1).map(EduExamRecord::getExamId).collect(Collectors.toSet());
                p = ExpressionUtils.and(p, q.id.in(recordExamIds));
            }
            // 5 待阅卷
            if (condition.getPcState() == 5) {
                Set<Long> recordExamIds = records.stream().filter(v -> v.getIsCheck() == 1).map(EduExamRecord::getExamId).collect(Collectors.toSet());
                p = ExpressionUtils.and(p, q.id.in(recordExamIds));
            }
        }

        return p;
    }

    @Override
    public EduDoExamBean loadExam(Long id, Long userId) {
        // EduExamRecord record = eduExamRecordDao.findFirstByUserIdAndExamIdOrderByIdDesc(userId, id);
        // Assert.isNull(record, "已经做过本次考试，不能再次做");
        EduDoExamBean bean = new EduDoExamBean();
        EduExam one = dao.getOne(id);
        if (new Date().after(one.getEndTime())){
            throw new IllegalArgumentException("该考试已结束");
        }
        long saveRandom = System.currentTimeMillis();
        // 重考是否需随机出卷
        boolean needRandom = this.resitNeedRandom(one, Context.getCurrentUserId());
        if (needRandom) {
            // 试卷ID
            List<EduExamTestPaper> examPapers = eduExamTestPaperDao.findByExamId(id);
            if (!CollectionUtils.isEmpty(examPapers) && examPapers.get(0) != null) {
                // 先删除该人员对应的试卷已关联的试题
                long deleteRandom = System.currentTimeMillis();
                eduTestPaperQuestionDao.deleteByExamIdAndTestPaperIdAndUserId(id, examPapers.get(0).getTestPaperId(), Context.getCurrentUserId());
                log.info("删除人员对应试卷："+(System.currentTimeMillis()-deleteRandom));
                 // 试卷重新出题
                long savaRandom = System.currentTimeMillis();
                this.saveRelatedTestPaperQuestion(one.getId(), examPapers.get(0).getTestPaperId(), Context.getCurrentUserId());
                log.info("试卷重新出题："+(System.currentTimeMillis()-savaRandom));
            }
        }
        log.info("重考是否需随机出卷："+(System.currentTimeMillis()-saveRandom));
        BeanUtils.copyProperties(one, bean);
        bean.setQuestionCount(dao.countOfQuestion(one.getId()));
        bean.setTypeName(commonTypeService.getNameById(one.getTypeId()));
        // 设置总分、及格分
        setTotalScoreAndPassScore(one.getId(), bean);
        bean.setQuestions(queryQuestionsOfExam(id, userId));
        if (one.getRandomizeQuestion() != null && one.getRandomizeQuestion() == 1) {
            EduDoExamResultBean resultBean = new EduDoExamResultBean();
            resultBean.setId(id);
            String s = cacheAnswers(resultBean, Context.getCurrentUserId(), 3);
            if (s == null) {
                Collections.shuffle(bean.getQuestions());
            } else {
                EduDoExamResultBean eduDoExamResultBean = JSON.parseObject(s, EduDoExamResultBean.class);
                if (org.apache.commons.collections4.CollectionUtils.isEmpty(eduDoExamResultBean.getAnswers())) {
                    Collections.shuffle(bean.getQuestions());
                }
            }
        }
        for (int i = 0; i < bean.getQuestions().size(); i++) {
            bean.getQuestions().get(i).setIndex(i + 1);
        }
        List<EduExamTestPaper> testPaperList = eduExamTestPaperDao.findByExamId(id);
        if (!CollectionUtils.isEmpty(testPaperList)) {
            Long testPaperId = testPaperList.get(0).getTestPaperId();
            Optional<EduTestPaper> testPaper = eduTestPaperDao.findById(testPaperId);
            bean.setRemark(testPaper.isPresent() ? testPaper.get().getRemark() : "");
        }
        String endExamTimeStamp = cacheExamTime(id, userId, 3);
        if (StringUtils.isEmpty(endExamTimeStamp)) {
            // 存储
            cacheExamTime(id, userId, 1);
            // 获取
            endExamTimeStamp = cacheExamTime(id, userId, 3);
        }
        bean.setEndExamTimeStamp(Integer.valueOf(endExamTimeStamp));
        return bean;
    }

    private List<Question> queryQuestionsOfExam(Long id, Long userId) {
        EduTestPaper eduTestPaper = testPaperService.findByExamId(id);
        QEduExamTestPaper paper = QEduExamTestPaper.eduExamTestPaper;
        QEduTestPaperQuestion tq = QEduTestPaperQuestion.eduTestPaperQuestion;
        QEduQuestion q = QEduQuestion.eduQuestion;
        QEduQuestionItem qi = QEduQuestionItem.eduQuestionItem;
        List<com.querydsl.core.Tuple> list = new ArrayList<>();
        //随机题，每个人看到的不一样,筛选用户id
        if (StringUtils.isNotBlank(eduTestPaper.getCreateMode()) && "random".equals(eduTestPaper.getCreateMode())) {
            long selectQ = System.currentTimeMillis();
            list = queryFactory.select(q, tq).from(paper, tq, q)
                    .where(tq.examId.eq(id), paper.testPaperId.eq(tq.testPaperId), tq.userId.eq(userId), tq.questionId.eq(q.id))
                    .orderBy(tq.indexNo.asc())
                    .fetch();
            log.info("查询随机耗时："+(System.currentTimeMillis()-selectQ)/1000+"S");
        /*    if (CollectionUtils.isEmpty(list)) {
                list = queryFactory.select(q, tq).from(paper, tq, q)
                        .where(paper.testPaperId.eq(tq.testPaperId), tq.userId.eq(userId), tq.questionId.eq(q.id))
                        .orderBy(tq.indexNo.asc())
                        .fetch();
            }*/

        } else {
            EduDoExamResultBean bean = new EduDoExamResultBean();
            bean.setId(id);
            String s = cacheAnswers(bean, Context.getCurrentUserId(), 3);
            if (s != null) {
                EduDoExamResultBean eduDoExamResultBean = JSON.parseObject(s, EduDoExamResultBean.class);
                if (org.apache.commons.collections4.CollectionUtils.isNotEmpty(eduDoExamResultBean.getAnswers())) {
                    return getQuestions(eduDoExamResultBean);
                }
            }
            long selectQ = System.currentTimeMillis();
            list = queryFactory.select(q, tq).from(paper, tq, q)
                    .where(paper.examId.eq(id), paper.testPaperId.eq(tq.testPaperId), tq.questionId.eq(q.id))
                    .orderBy(tq.indexNo.asc())
                    .fetch();
            log.info("查询试题耗时："+(System.currentTimeMillis()-selectQ)/1000+"S");
        }
        list = list.stream().distinct().collect(Collectors.toList());
        System.out.println(list.size());
        Map<Long, Question> map = new LinkedHashMap<>();
        for (com.querydsl.core.Tuple t : list) {
            EduQuestion eduQuestion = t.get(q);
            EduTestPaperQuestion tpq = t.get(tq);

            List<EduQuestionItem> eduQuestionItems = eduQuestionItemDao.findByQuestionIdOrderByIndexNoAsc(eduQuestion.getId());
//            EduQuestionItem item = t.get(qi);
            // 使用EduTestPaperQuestion.id, 即使题目相同，也会添加到队列里，如果使用EduQuestion.id,那么会合并相同问题及选项
            Question question = map.get(tpq.getId());
            if (question == null) {
                question = convertQuestion(eduQuestion, tpq);
                map.put(tpq.getId(), question);
            }
            for (EduQuestionItem item : eduQuestionItems) {
                question.getOptions().add(convertQuestionItem(item));
            }
        }
        System.out.println(map.size());
        return new ArrayList<>(map.values());
    }

    /**
     * 读取缓存的试题和答案
     *
     * @param s
     * @return
     */
    public List<Question> getQuestions(EduDoExamResultBean eduDoExamResultBean) {
        List<Question> list = new ArrayList<>();

        List<EduDoExamResultBean.UserAnswer> answers = eduDoExamResultBean.getAnswers();
        List<Long> ids = answers.stream().map(EduDoExamResultBean.UserAnswer::getId).collect(Collectors.toList());
        List<Long> qIds = answers.stream().map(EduDoExamResultBean.UserAnswer::getQid).collect(Collectors.toList());
        List<EduTestPaperQuestion> edpqs = eduTestPaperQuestionDao.findbyIds(ids);
        Map<Long, EduTestPaperQuestion> eduTestPaperQuestionMap = edpqs.stream().collect(Collectors.toMap(EduTestPaperQuestion::getId, r -> r));
        List<EduQuestion> eduQuestions = eduQuestionDao.findByIds(qIds);
        Map<Long, EduQuestion> eduQuestionMap = eduQuestions.stream().collect(Collectors.toMap(EduQuestion::getId, r -> r));

        List<EduQuestionItem> questionItems = eduQuestionItemDao.findByQuestionIds(qIds);
        Map<Long, List<EduQuestionItem>> eduQuestionItemMap = questionItems.stream().collect(Collectors.groupingBy(e -> e.getQuestionId()));

        for (int i = 0; i < answers.size(); i++) {
            Question question = new Question();
            EduQuestion eduQuestion = eduQuestionMap.get(answers.get(i).getQid());
            question.setAnswer(answers.get(i).getAnswer());
            question.setId(answers.get(i).getId());
            question.setIndex(i);
            question.setQid(answers.get(i).getQid());
            question.setName(eduQuestion.getName());
            question.setOptions(convertQuestionItems(eduQuestionItemMap.get(answers.get(i).getQid())));
            question.setScore(formatString(eduTestPaperQuestionMap.get(answers.get(i).getId()).getScore()));
            question.setTypeName(QuestionTypes.findNameByCode(eduQuestion.getType()));
            question.setType(eduQuestion.getType());
            list.add(question);
        }
        return list;
    }

    public List<QuestionItem> convertQuestionItems(List<EduQuestionItem> items) {
        List<QuestionItem> list = new ArrayList<>();
        if (!CollectionUtils.isEmpty(items)) {
            for (EduQuestionItem item : items) {
                list.add(convertQuestionItem(item));
            }
        }
        return list;
    }

    private QuestionItem convertQuestionItem(EduQuestionItem item) {
        QuestionItem qi = new QuestionItem();
        qi.setChoiceCode(item.getChoiceCode());
        qi.setContent(item.getContent());
        qi.setId(item.getId());
        return qi;
    }

    private Question convertQuestion(EduQuestion eduQuestion, EduTestPaperQuestion tpq) {
        Question q = new Question();
        q.setId(tpq.getId());
        q.setQid(eduQuestion.getId());
        q.setAnswer(eduQuestion.getAnswer());
        q.setName(eduQuestion.getName());
        q.setScore(formatString(tpq.getScore()));
        q.setTypeName(QuestionTypes.findNameByCode(eduQuestion.getType()));
        q.setType(eduQuestion.getType());
        q.setOptions(new ArrayList<EduDoExamBean.QuestionItem>(4));
        return q;
    }

    private void setTotalScoreAndPassScore(Long id, EduDoExamBean bean) {
        Tuple tuple = dao.sumTotalScoreAndPassScore(id);
        if (tuple != null) {
            double totalScore = tuple.get("totalScore", Number.class).floatValue();
            bean.setTotalScore(formatString(totalScore));
            bean.setPassScore(formatString(tuple.get("passScore", Number.class).doubleValue()));
        }
    }

    private String formatString(Double totalScore) {
        Double delta = totalScore - totalScore.intValue();
        return delta < 0.0001 ? String.valueOf(totalScore.intValue()) : String.valueOf(totalScore);
    }


    @Override
    public Boolean checkExamContainZgt(Long examId) {
        EduTestPaper testPaper = eduTestPaperDao.findByExamId(examId);
        return eduTestPaperService.checkTestPaperContainZgt(testPaper);
    }


    @Override
    @NoRepeatSubmit(paramNames = "id,score")
    public void submitMarkingPapers(List<EduDoExamResultBean.ExamUserAnswer> examUserAnswerList) {
        Long examRecordId = null;
        float score = 0;
        for (EduDoExamResultBean.ExamUserAnswer examUserAnswer : examUserAnswerList) {
            EduExamUserAnswer eduExamUserAnswer = eduExamUserAnswerDao.findById(examUserAnswer.getId()).get();
            if (eduExamUserAnswer.getScore() != null && eduExamUserAnswer.getScore() > 0) {
                throw new IllegalArgumentException("无需重复打分");
            }
            examRecordId = eduExamUserAnswer.getExamRecordId();
            if (examUserAnswer.getScore() != null) {
                score += examUserAnswer.getScore();
            }
            eduExamUserAnswer.setScore(examUserAnswer.getScore());
            eduExamUserAnswerDao.save(eduExamUserAnswer);
        }

        EduExamRecord eduExamRecord = eduExamRecordDao.findById(examRecordId).get();
        float aFloat = Float.valueOf(eduExamRecord.getScore());
        eduExamRecord.setScore(String.valueOf(aFloat + score));
        eduExamRecord.setIsCheck(2);
        eduExamRecord.setState(Float.valueOf(eduExamRecord.getScore()) >= Float.valueOf(eduExamRecord.getPassScore()) ? 1 : 0);
        eduExamRecordDao.save(eduExamRecord);

        int recordCount = eduExamRecordDao.countByExamIdAndUserId(eduExamRecord.getId(), eduExamRecord.getUserId());
        boolean isFirstExam = true;
        if (recordCount > 1) {
            isFirstExam = false;
        }
        if (eduExamRecord.getState() == 1) {
            validTraining(eduExamRecord.getExamId(), eduExamRecord.getUserId());
        } else {
            //不通过
            validTrainingResult(eduExamRecord.getExamId(), eduExamRecord.getUserId());
        }
        eduCourseStatisticsService.addExamCount(null, eduExamRecord.getExamId(), eduExamRecord.getUserId(), false, eduExamRecord.getState() == 1, isFirstExam);
    }

    @Override
    public ExamResult submit(EduDoExamResultBean result, Long currentUserId, Integer isSubmit) {
        // 检查有没有做问卷的权限
        checkExamPrivilege(currentUserId, result.getId());
        EduExam exam = dao.findById(result.getId()).get();
        if (!DateUtil.isSameTime(exam.getReleaseTime(),result.getReleaseTime())){
            cacheExamTime(result.getId(), currentUserId, 2);
                          Assert.isTrue(false,
                        "当前考试已被取消,不可提交");
        }
        /*if (isSubmit == 0) {
            Assert.isTrue(isExamInEffectiveTimeRange(exam), "考试未开始或者已经结束，不能再提交");
        }*/
        EduExamRecord record = eduExamRecordDao.selectByUserIdAndExamIdLatest(currentUserId, result.getId());
        if (record != null) {
            Assert.isTrue(!Constant.NOT_ALLOW_TRY_AGAIN.equals(exam.getAllowTryAgain()), "已经做过本次考试，不允许重考，不能再次做");
            // 选择了随时可重考，不校验重考次数
            if (!Constant.ANY_TIME_ALLOW_TRY_AGAIN.equals(exam.getAllowTryAgain()) && isSubmit == 0) {
                Assert.isTrue(
                        exam.getTryAgainLimit() == 0
                                || (exam.getTryAgainLimit() != 0 && record.getSubmitTimes() - 1 < exam.getTryAgainLimit()),
                        "重考次数已达到限制，不能再提交");
            }
        }
        //查询记录表中是否存在记录，如果存在则代表不是第一次参加考试，此次考试则不加APP考试次数,通过状态会改变
        int recordCount = eduExamRecordDao.countByExamIdAndUserId(exam.getId(), currentUserId);
        boolean isFirstExam = true;
        if (recordCount != 0) {
            isFirstExam = false;
        }
        EduExamRecord re = new EduExamRecord();
        re.setExamId(result.getId());
        setTotalScoreAndPassScore(result.getId(), re);

        re.setUserId(currentUserId);
        re.setTenentId(Context.getCurrentTenantId() != null ? Context.getCurrentTenantId() : result.getTenentId());
        re.setOrgCode(Context.getCurrentOrgCode() != null ? Context.getCurrentOrgCode() : result.getOrgCode());
        re.setUpdateBy(currentUserId);
        re.setCreateBy(currentUserId);
        // 考试总耗时

        String redisKey = String.format(CACHE_EXAM_TIME, exam.getId(), currentUserId);
        String json = redisClient.get(redisKey);
        JSONObject jsonObject = JSONUtil.parseObj(json);
        String startTime = jsonObject.getStr("startTime");
        String endTime = jsonObject.getStr("endTime");
        if (jsonObject.isEmpty()){
            EduExamRecord eduExamRecord = eduExamRecordsMapper.selectUserExamRecord(currentUserId, result.getId());
            if (!ObjectUtils.isEmpty(eduExamRecord)){
                Integer isCheck = eduExamRecord.getIsCheck();
                boolean canTryAgain= false;
                boolean passed = eduExamRecord.getState() == 1;
                if (isCheck!=0){
                    canTryAgain=false;
                }
                if (Constant.ALLOW_TRY_AGAIN.equals(exam.getAllowTryAgain())) {
                    canTryAgain = !passed && (eduExamRecord.getSubmitTimes() < exam.getTryAgainLimit() + 1 || Constant.TRY_AGAIN_LIMIT_OFF.equals(exam.getTryAgainLimitFlag()));
                }
                if (Constant.ANY_TIME_ALLOW_TRY_AGAIN.equals(exam.getAllowTryAgain())) {
                    canTryAgain = exam.getEndTime().getTime() > new Date().getTime();
                }
                return new ExamResult(eduExamRecord.getId(), eduExamRecord.getScore(), passed, eduExamRecord.getRightCount(),
                        eduExamRecord.getWrongCount(), canTryAgain, isCheck==0 ? 0:1);
            }
             return null;
        }
        if (DateUtil.parseDateTime(endTime).getTime() > DateUtil.current()) {
            re.setSubmitTime(new Date());
        } else {
            re.setSubmitTime(DateUtil.parseDateTime(endTime));
        }
        re.setCostTime((re.getSubmitTime().getTime()) - (DateUtil.parseDateTime(startTime).getTime()));

        // 计算分数
        List<EduExamUserAnswer> examDetails = new ArrayList<>(result.getAnswers().size());
        Map<String, Float> map = new HashMap<>();
        try {
            map = calculateScore(result, examDetails, currentUserId);
        } catch (Exception e) {
        }
        re.setScore(String.valueOf(map.get("score")));
        re.setRightCount(map.get("rightCount") == null ? 0 : map.get("rightCount").intValue());
        re.setWrongCount(map.get("wrongCount") == null ? 0 : map.get("wrongCount").intValue());
        re.setSubmitTimes(record == null ? 1 : record.getSubmitTimes() + 1);
        boolean passed = false;
        boolean canTryAgain = false;
        // 判断是否包含主观题
        if (checkExamContainZgt(exam.getId())) {
            // 未阅卷
            re.setIsCheck(1);
            re.setState(0);
            canTryAgain = false;
        } else {
            // 不需要阅卷
            re.setIsCheck(0);
            if (re.getScore().equals("null")) {
                re.setScore("0");
            }
            re.setState(Float.valueOf(re.getScore()) >= re.getPassScore() ? 1 : 0);
            passed = re.getState() == 1;
            // 是否可重考（app端考试提交答案后 可重考时要求直接显示重考按钮）
            // 可重考条件：1.设置不可重考时，不可重考 2.可重考时，未通过且在重考次数限制之内的可重考 3.随时可重考时，在截止日期之前都可重考
            if (Constant.ALLOW_TRY_AGAIN.equals(exam.getAllowTryAgain())) {
                canTryAgain = !passed && (re.getSubmitTimes() < exam.getTryAgainLimit() + 1 || Constant.TRY_AGAIN_LIMIT_OFF.equals(exam.getTryAgainLimitFlag()));
            }
            if (Constant.ANY_TIME_ALLOW_TRY_AGAIN.equals(exam.getAllowTryAgain())) {
                canTryAgain = exam.getEndTime().getTime() > new Date().getTime();
            }
        }
        if (StringUtils.isNotBlank(result.getAutograph())) {
            re.setAutograph(result.getAutograph());
            re.setRotatePath(uploadImage(result.getAutograph()));
        }

        eduExamRecordDao.save(re);
        //考试是否通过
        if (re.getState() == 1) {
            //考试id
            validTraining(result.getId(), currentUserId);
        } else {
            //考试包含主观题 等阅卷后再修改培训结果
            if (!checkExamContainZgt(result.getId())) {
                validTrainingResult(result.getId(), currentUserId);
            }
        }
        // 填充记录ID信息
        examDetails.forEach(d -> {
            d.setExamRecordId(re.getId());
            d.setTenentId(Context.getCurrentTenantId() != null ? Context.getCurrentTenantId() : result.getTenentId());
            d.setOrgCode(Context.getCurrentOrgCode() != null ? Context.getCurrentOrgCode() : result.getOrgCode());
        });
        // 保存用户的详细答案
        long savaUserId = System.currentTimeMillis();
        eduExamUserAnswerDao.saveAll(examDetails);
        log.info("保存用户的详细答案："+(System.currentTimeMillis()-savaUserId));
        //增加是否是第一次考试的判断
        long savaExamId = System.currentTimeMillis();
        eduCourseStatisticsService.addExamCount(result, result.getId(), currentUserId, true, passed, isFirstExam);
        log.info("增加是否是第一次考试的判断："+(System.currentTimeMillis()-savaUserId));
        cacheExamTime(result.getId(), currentUserId, 2);

        // 如果设置了随时重考，都可重考
        return new ExamResult(re.getId(), re.getScore(), passed, map.get("rightCount").intValue(),
                map.get("wrongCount").intValue(), canTryAgain, re.getIsCheck());
    }

    private void validTraining(Long examId, Long currentUserId) {
        List<EduTrainningRecord> eduTrainningRecords = eduTrainningRecordMapper.selectByExamIdUnFinish(examId);
        if (CollectionUtils.isEmpty(eduTrainningRecords)) {
            return;
        }
        List<Long> userIds = new ArrayList<>();
        userIds.add(currentUserId);
        eduTrainningRecords.forEach(eduTrainningRecord -> {
            if (TrainingQualifiedStandard.PASS_EXAM.getId() == eduTrainningRecord.getTrainingQualifiedStandard()) {
                //考试通过 修改培训结果
                eduTrainningRecordUserMapper.updateTrainingResultByUserIds(eduTrainningRecord.getId(), "Y", userIds);
            } else if (TrainingQualifiedStandard.FINISH_COURSE_EXAM.getId() == eduTrainningRecord.getTrainingQualifiedStandard()) {
                //培训课程下的课件
                List<Long> trainingCoursewares = eduTrainningRecordMapper.selectCoursewareByTrainingId(eduTrainningRecord.getId());
                //用户学完的课件
                List<Long> learns = eduTrainningRecordUserMapper.selectUserLearnCourseware(currentUserId, trainingCoursewares, 1, eduTrainningRecord.getId());
                //是否学完课程
                if (new HashSet<>(learns).containsAll(trainingCoursewares)) {
                    eduTrainningRecordUserMapper.updateTrainingResultByUserIds(eduTrainningRecord.getId(), "Y", userIds);
                }
            } else if (TrainingQualifiedStandard.FINISH_SIGN_IN_EXAM.getId() == eduTrainningRecord.getTrainingQualifiedStandard()) {
                //是否签到
                if (eduTrainningRecordUserMapper.queryRecordsByUserIdAndTrainingId(eduTrainningRecord.getId(), currentUserId) == 1) {
                    eduTrainningRecordUserMapper.updateTrainingResultByUserIds(eduTrainningRecord.getId(), "Y", userIds);
                }
            }
        });
    }

    private void validTrainingResult(Long examId, Long userId) {
        List<EduTrainningRecord> eduTrainningRecords = eduTrainningRecordMapper.selectByExamIdUnFinish(examId);
        if (CollectionUtils.isEmpty(eduTrainningRecords)) {
            return;
        }
        List<Long> userIds = new ArrayList<>();
        userIds.add(userId);
        eduTrainningRecords.forEach(eduTrainningRecord -> {
            if (TrainingQualifiedStandard.PASS_EXAM.getId() == eduTrainningRecord.getTrainingQualifiedStandard()) {
                //考试未通过
                eduTrainningRecordUserMapper.updateTrainingResultByUserIds(eduTrainningRecord.getId(), "N", userIds);
            }else if (TrainingQualifiedStandard.FINISH_COURSE.getId()==eduTrainningRecord.getTrainingQualifiedStandard()){
                //学完课程 考试不通过
                //培训课程下的课件
                List<Long> trainingCoursewares = eduTrainningRecordMapper.selectCoursewareByTrainingId(eduTrainningRecord.getId());
                //用户学完的课件
                List<Long> learns = eduTrainningRecordUserMapper.selectUserLearnCourseware(userId, trainingCoursewares, 1,eduTrainningRecord.getId());
                //学完课程 考试不通过修改培训结果
                if (new HashSet<>(learns).containsAll(trainingCoursewares)) {
                    eduTrainningRecordUserMapper.updateTrainingResultByUserIds(eduTrainningRecord.getId(), "N", userIds);
                }
            }else if (TrainingQualifiedStandard.FINISH_SIGN_IN_EXAM.getId()==eduTrainningRecord.getTrainingQualifiedStandard()){
                //已签到 考试不通过修改培训结果
                if (eduTrainningRecordUserMapper.queryRecordsByUserIdAndTrainingId(eduTrainningRecord.getId(), userId) == TrainingUserSignStatus.OFFLINE.getId()) {
                    eduTrainningRecordUserMapper.updateTrainingResultByUserIds(eduTrainningRecord.getId(), "N", userIds);
                }
            }
        });
    }

    /**
     * @param examId        考试id
     * @param currentUserId 用户id
     * @param handle        处理方式  1 新增  2 删除 3 获取
     * @return 考试还剩多少秒
     */
    public String cacheExamTime(Long examId, Long currentUserId, Integer handle) {
        String redisKey = String.format(CACHE_EXAM_TIME, examId, currentUserId);
        if (handle == 1) {
            EduExam exam = dao.getOne(examId);
            Date now = new Date();
            String endTime = DateUtil.formatDateTime(DateUtil.offsetMinute(now, exam.getDurationMinutes()));
            Map<String, Object> map = new HashMap<>();
            // 开始考试时间
            map.put("startTime", DateUtil.formatDateTime(now));
            // 结束考试时间
            map.put("endTime", endTime);
            redisClient.set(redisKey, JSONUtil.toJsonStr(map));
            // 用于定时任务
            cacheExamTask(examId, currentUserId, endTime, 1);
        } else if (handle == 2) {
            redisClient.removeKey(redisKey);
            cacheExamTask(examId, currentUserId, null, 2);
        } else {
            String json = redisClient.get(redisKey);
            if (StringUtils.isEmpty(json)) {
                return "";
            }
            JSONObject jsonObject = JSONUtil.parseObj(json);
            String endTime = jsonObject.getStr("endTime");
            if (StringUtils.isNotBlank(endTime)) {
                if (DateUtil.parse(endTime, DateUtils.STYLE_FULL).getTime() < System.currentTimeMillis()) {
                    return "";
                }
            }
            return (DateUtil.betweenMs(new Date(), DateUtil.parseDateTime(endTime)) / 1000) + "";
        }
        return "";
    }

    /**
     * 用于 定时查询 考试是否到时
     *
     * @param examId        考试id
     * @param currentUserId 用户id
     * @param examEndTime   考试到时时间
     * @param handle        处理方式  1 新增  2 删除 3 获取
     * @return
     */
    public RScoredSortedSet<String> cacheExamTask(Long examId, Long currentUserId, String examEndTime, Integer handle) {
        String key = "CACHE_EXAM_TASK";
        String value = examId + "-" + currentUserId;
        if (handle == 1) {
            long score = DateUtil.parseDateTime(examEndTime).getTime();
            redisClient.setZSet(key, value, score);
        } else if (handle == 2) {
            redisClient.removeZSet(key, value);
        } else {
            return redisClient.getZSet(key);
        }

        return null;
    }


    public void delAllCacheAnswers(Long examId, Long userId) {
        if (examId != null) {
            EduDoExamResultBean bean = new EduDoExamResultBean();
            bean.setId(Convert.toLong(examId));
            cacheAnswers(bean, userId, 2);
        }
    }

    @Override
    public String cacheAnswers(EduDoExamResultBean result, Long currentUserId, Integer handle) {
        //String redisKeyTime = String.format(CACHE_EXAM_TIME, result.getId(), currentUserId);
        String redisKey = "CACHE_ANSWERS:" + result.getId() + ":" + currentUserId;
        String redisKeyMinutees = "CACHETES_MINUTES:" + result.getId() + ":" + currentUserId;
        String redisKeyEndTime = "CACHETES_SECONDS:" + result.getId() + ":" + currentUserId;
        String redisSwers = "ANSWERS_CACHE:" + "swers" + ":" + currentUserId;
        String redisMinutes= "ANSWERS_CACHE:" + "minutes" + ":" +currentUserId;
        result.setResultByUserId(currentUserId);
        // 判断 ==1 用户手动提交 删除缓存
        if (result.getIsSubmit() != null && result.getIsSubmit().equals(1)) {
            handle = 2;
        }
        // 新增/修改
        if (handle == 1) {
            result.setTenentId(Context.getCurrentTenantId());
            result.setOrgCode(Context.getCurrentOrgCode());
            redisClient.set(redisKey, JSONUtil.toJsonStr(result));
            if (StringUtils.isBlank(redisClient.get(redisKeyMinutees))) {
                EduDoExamResultBean eduDoExamResultBean = new EduDoExamResultBean();
                eduDoExamResultBean.setId(result.getId());
                EduExam one = dao.getOne(result.getId());
                long between = DateUtil.between(new Date(), one.getEndTime(), DateUnit.SECOND);
                redisClient.setEx(redisKeyMinutees, JSONUtil.toJsonStr(result), one.getDurationMinutes() * 60 + 30, TimeUnit.SECONDS);
                redisClient.setEx(redisKeyEndTime, JSONUtil.toJsonStr(result), between + 30, TimeUnit.SECONDS);
                redisClient.setEx(redisSwers, JSONUtil.toJsonStr(eduDoExamResultBean), one.getDurationMinutes() * 60, TimeUnit.SECONDS);
                redisClient.setEx(redisMinutes,JSONUtil.toJsonStr(eduDoExamResultBean), between, TimeUnit.SECONDS);
            }
        } else if (handle == 2) {
            // 删除
            redisClient.removeKey(redisKey);
            redisClient.removeKey(redisKeyMinutees);
            redisClient.removeKey(redisKeyEndTime);
            redisClient.removeKey(redisSwers);
            redisClient.removeKey(redisMinutes);
            cacheExamTask(result.getId(), currentUserId, null, 2);

        } else {
            // 获取数据
            return redisClient.get(redisKey);
        }

        return "";
    }

    @Override
    public Object selectNoTrainningExam(SearchEduExam condition) {
        com.baomidou.mybatisplus.extension.plugins.pagination.Page<EduExam> page = new com.baomidou.mybatisplus.extension.plugins.pagination.Page<>(condition.getPage() + 1, condition.getSize());
        com.baomidou.mybatisplus.extension.plugins.pagination.Page<EduExamListBean> listBeanPage = eduExamMapper.selectNoTrainningExam(page, condition);
        List<EduExamListBean> records = listBeanPage.getRecords();
        records.forEach(exam -> {
            //查询考试关联的课程详情
            //  exam.setEduCourses(eduExamMapper.selectExamCourseDetails(exam.getId()));
            exam.setStateName(ExamStates.findNameById(exam.getState()));
            exam.setFormTypeName(TrainingFrom.findNameById(Math.toIntExact(exam.getFormType())));
        });
        return ResultBean.pageData(records, listBeanPage.getTotal());
    }

    public String uploadImage(String originalPath) {
        try {
            int i = originalPath.indexOf("=");
            String id = originalPath.substring(i + 1, originalPath.length());
            BaseImageRepository image = imageService.findById(Long.parseLong(id));
            String path = imageUploadDir + File.separator + image.getOriginPath();
            File picture = new File(path);
            BufferedImage sourceImg = ImageIO.read(new FileInputStream(picture));


            BufferedImage rotate = RotateImageUtil.rotateImage(sourceImg, -90);
            String subdir = DateUtil.format(new Date(), DatePattern.PURE_DATE_PATTERN);
            String dir = imageUploadDir + "/" + subdir + File.separator + UUID.randomUUID().toString() + ".png";
            FileUtils.forceMkdir(new File(dir));
            ImageIO.write(rotate, "png", new File(dir));
//            InputStream is = new FileInputStream(new File(dir, picture.getName()));
//            baseAttachmentRepositoryService.saveFile(new File(imageUploadDir+subdir+ File.separator , picture.getName()));

//            String rotatePath = Md5Util.getMd5ByFile(is);

            return dir;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 计算分数，并生成细节记录
     *
     * @param result
     * @param examDetails
     * @return
     */
    private Map<String, Float> calculateScore(EduDoExamResultBean result, List<EduExamUserAnswer> examDetails, Long currentUserId) {
        float score = 0;
        float rightCount = 0;
        float wrongCount = 0;
        List<Long> pids = result.getAnswers().stream()
                .map(com.joysuch.wwyt.edu.bean.EduDoExamResultBean.UserAnswer::getQid).collect(Collectors.toList());
        List<Long> testPaperQuestionIds = result.getAnswers().stream()
                .map(com.joysuch.wwyt.edu.bean.EduDoExamResultBean.UserAnswer::getId).collect(Collectors.toList());
        List<EduQuestion> questions = eduQuestionDao.findAllById(pids);
        List<EduTestPaperQuestion> paperQuestions = eduTestPaperQuestionDao.findAllById(testPaperQuestionIds);

        Map<Long, EduTestPaperQuestion> testPaperQuestionMap = new HashMap<>();
        Map<Long, EduQuestion> map = new HashMap<>();
        questions.forEach(q -> map.put(q.getId(), q));
        paperQuestions.forEach(q -> testPaperQuestionMap.put(q.getId(), q));
        for (com.joysuch.wwyt.edu.bean.EduDoExamResultBean.UserAnswer q : result.getAnswers()) {
            EduQuestion eq = map.get(q.getQid());
            EduExamUserAnswer detail = new EduExamUserAnswer();
            detail.setAnswer(q.getAnswer());
            if (!eq.getType().equals(QuestionTypes.ZG_CHOICE.getCode())) {
                detail.setCorrectFlag("N");
                detail.setScore(0.0d);
            }
            detail.setQuestionId(q.getQid());
            detail.setTestPaperQuestionId(q.getId());
            detail.setTenentId(Context.getCurrentTenantId() != null ? Context.getCurrentTenantId() : result.getTenentId());
            detail.setCreateBy(currentUserId);
            detail.setUpdateBy(currentUserId);
            detail.setOrgCode(Context.getCurrentOrgCode() != null ? Context.getCurrentOrgCode() : result.getOrgCode());
            examDetails.add(detail);
            if (q.getAnswer() == null || eq.getType().equals(QuestionTypes.ZG_CHOICE.getCode())) {
                continue;
            }

            EduTestPaperQuestion tq = testPaperQuestionMap.get(q.getId());
            // System.out.println("question:" + q.getQid() + ".answer=" + eq.getAnswer() + ",userAnswer=" +
            // q.getAnswer());
            if (eq != null && tq != null) {
                boolean answerRight = isAnswerRight(eq, q);
                if (answerRight) {
                    score += tq.getScore() == null ? 0 : tq.getScore();
                    detail.setCorrectFlag("Y");
                    detail.setScore(tq.getScore());
                    rightCount += 1;
                } else {
                    wrongCount += 1;
                }
            }
        }
        Map<String, Float> resultMap = new HashMap<>(3);
        resultMap.put("score", score);
        resultMap.put("rightCount", rightCount);
        resultMap.put("wrongCount", wrongCount);
        return resultMap;
    }

    private boolean isAnswerRight(EduQuestion eq, com.joysuch.wwyt.edu.bean.EduDoExamResultBean.UserAnswer q) {
        QuestionTypes type = QuestionTypes.findByCode(eq.getType());
        if (type == QuestionTypes.SINGLE || type == QuestionTypes.JUDGE) {
            if (eq.getAnswer().equals(q.getAnswer())) {
                return true;
            }
        } else if (type == QuestionTypes.MULTI_CHOICE) {
            String[] statndAnswers = eq.getAnswer().split(",");
            String[] userAnswers = q.getAnswer().split(",");
            // 排序答案
            Arrays.sort(userAnswers);
            Arrays.sort(statndAnswers);
            if (Arrays.equals(statndAnswers, userAnswers)) {
                return true;
            }
        }
        return false;
    }

    private void setTotalScoreAndPassScore(Long id, EduExamRecord re) {
        Tuple tuple = dao.sumTotalScoreAndPassScore(id);
        if (tuple != null) {
            re.setTotalScore(tuple.get("totalScore", Number.class).floatValue());
            re.setPassScore(tuple.get("passScore", Number.class).floatValue());
        }
    }

    private void checkExamPrivilege(Long currentUserId, Long id) {
        // TODO Auto-generated method stub

    }

    @Override
    public EduExamUserRecordBean queryExamUserRecords(Long examId, List<Long> userIdList) {
        List<EduExamRecord> records = eduExamRecordDao.findByExamIdAndUserIdIn(examId, userIdList);
        EduExamUserRecordBean bean = new EduExamUserRecordBean();
        bean.setExamId(examId);
        bean.setPassScore(dao.sumPassScore(examId));
        List<UserRecord> userRecords = records.stream().map(e -> {
            UserRecord ur = new UserRecord();
            ur.setUserId(e.getUserId());
            ur.setScore(e.getScore());
            return ur;
        }).collect(Collectors.toList());
        bean.setUsers(userRecords);
        return bean;
    }

    @Override
    public Page<EduExamCenterListBean> queryUserExamRecordPage(Pageable page, Long userId,
                                                               AppSearchEduExamRecord condition) {
        Set<Long> examIds = queryPossibleUserExamIds(userId, condition);
        Predicate p = createExamRecordCondition(examIds, condition);
        Page<EduExam> all = dao.findAll(p, page);
        return convertExamRecords(userId, all);
    }

    @Override
    public ResultBean eduExamRelease() {
        List<EduExam> list = dao.findByState(1);
        if (CollectionUtils.isEmpty(list)) {
            return ResultBean.success(EduExamReleaseDto.defaultDto());
        }
        Map<Integer, Long> map = list.stream().filter(r -> {
            Date startTime = r.getStartTime();
            if (startTime == null) {
                return false;
            }
            LocalDateTime localDateTime = DateTimeUtil.date2LocalDateTime(startTime);
            return localDateTime.getYear() == LocalDate.now().getYear();
        }).collect(Collectors.groupingBy((EduExam r) -> {
            Date startTime = r.getStartTime();
            int monthValue = DateTimeUtil.date2LocalDateTime(startTime).getMonthValue();
            return monthValue;
        }, Collectors.counting()));
        for (int i = 1; i <= 12; i++) {
            map.putIfAbsent(i, 0L);
        }
        TreeMap<Integer, Long> treeMap = new TreeMap<>(map);
        return ResultBean.success(new EduExamReleaseDto(convertMonth(treeMap.keySet()),
                treeMap.values().stream().collect(Collectors.toList())));
    }

    private List<String> convertMonth(Set<Integer> set) {
        return Lists.newArrayList(set).stream().map(r -> r + "月").collect(Collectors.toList());
    }

    /**
     * @param userId
     * @param condition
     * @return
     */
    protected Set<Long> queryPossibleUserExamIds(Long userId, AppSearchEduExamRecord condition) {
        List<EduExamRecord> records;
        if (condition.getState() != null) {
            Integer state = 0;
            if ("passed".equals(condition.getState())) {
                state = 1;
            }
            records = eduExamRecordDao.findByUserIdAndState(userId, state);
        } else {
            records = eduExamRecordDao.selectByUserIdLatest(userId);
        }
        System.err.println(condition.getState());
        Set<Long> examIds = records.stream().map(EduExamRecord::getExamId).collect(Collectors.toSet());
        return examIds;
    }

    private Predicate createExamRecordCondition(Collection<Long> examIds, AppSearchEduExamRecord condition) {
        QEduExam examQuery = QEduExam.eduExam;
        // 只能查看已发布的问卷
        Predicate expression = ExpressionUtils.and(examQuery.id.in(examIds), examQuery.state.eq(ExamStates.PUBLISHED.getId()));
        if (condition.getName() != null) {
            expression = ExpressionUtils.and(expression, examQuery.name.contains(condition.getName()));
        }
        if (condition.getTypeId() != null) {
            expression = ExpressionUtils.and(expression, examQuery.typeId.eq(condition.getTypeId()));
        }

        return expression;
    }

    @Override
    public List<EduTestPaperQuestionDetailBean> queryQuestionListByExamId(Long id, Integer showAnswer, Long userId, Long examRecordId) {
        List<EduExamTestPaper> testPapers = eduExamTestPaperDao.findByExamId(id);
        if (testPapers.size() > 0) {
            Long testPaperId = testPapers.get(0).getTestPaperId();
            List<Long> testPaperQuestionIds = null;
            if (examRecordId != null) {
                List<EduExamUserAnswer> answers = examUserAnswerDao.findByExamRecordId(examRecordId);
                testPaperQuestionIds = answers.stream().map(EduExamUserAnswer::getTestPaperQuestionId).collect(Collectors.toList());
            }
            List<EduTestPaperQuestionDetailBean> questionList = testPaperService.getQuestionList(testPaperId, true, userId, id, testPaperQuestionIds);
            EduExam exam = dao.getOne(id);
            // 不显示答案的考试隐藏此字段.showAnswer=1时查询answer答案,不管考试设置是不是显示答案
            if (showAnswer == null || showAnswer != 1) {
                if (Constant.NOT_ALLOW_TRY_AGAIN.equals(exam.getShowAnswerInRecord())) {
                    questionList.forEach(question -> question.setAnswer(""));
                }
            }
            return questionList;
        }
        return Collections.EMPTY_LIST;
    }


    @Override
    public List<EduTestPaperQuestionDetailBean> queryMarkingPapersList(Long examId, Long userId, Long examRecordId, String questionType, Integer isScore) {
        List<EduExamTestPaper> testPapers = eduExamTestPaperDao.findByExamId(examId);
        if (testPapers.size() > 0) {
            Long testPaperId = testPapers.get(0).getTestPaperId();
            List<EduExamUserAnswer> answers = examUserAnswerDao.findByExamRecordId(examRecordId);
            List<Long> testPaperQuestionIds = answers.stream().map(EduExamUserAnswer::getTestPaperQuestionId).collect(Collectors.toList());
            List<EduTestPaperQuestionDetailBean> questionList = testPaperService.getQuestionList(testPaperId, true, userId, examId, testPaperQuestionIds);
            List<EduTestPaperQuestionDetailBean> sortList = Lists.newArrayList();
            for (int i = 0; i < answers.size(); i++) {
                EduExamUserAnswer answer = answers.get(i);
                EduTestPaperQuestionDetailBean bean = new EduTestPaperQuestionDetailBean();
                bean.setQuestionId(answer.getQuestionId());
                bean.setSort(i);
                sortList.add(bean);
            }
            Map<Long, EduExamUserAnswer> answerMap = answers.stream().collect(Collectors.toMap(EduExamUserAnswer::getQuestionId, e -> e));
            // 考题排序
            Map<Long, Integer> sortMap = sortList.stream().collect(Collectors.toMap(EduTestPaperQuestionDetailBean::getQuestionId, EduTestPaperQuestionDetailBean::getSort));
            for (EduTestPaperQuestionDetailBean questionDetailBean : questionList) {
                if (questionType != null && !questionDetailBean.getType().equals(questionType)) {
                    continue;
                }
                EduExamUserAnswer r = answerMap.get(questionDetailBean.getQuestionId());
                if (r == null) {
                    continue;
                }
                if (isScore != null && isScore == 1 && r.getScore() == null) {
                    continue;
                }
                if (isScore != null && isScore == 0 && r.getScore() != null) {
                    continue;
                }
                questionDetailBean.setSort(sortMap.get(r.getQuestionId()));

                EduExamUserAnswerDetailBean bean = new EduExamUserAnswerDetailBean();
                bean.setId(r.getId());
                bean.setCorrectFlag(r.getCorrectFlag());
                bean.setCorrect("Y".equals(r.getCorrectFlag()));
                bean.setExamRecordId(examRecordId);
                bean.setAnswer(r.getAnswer());
                bean.setQuestionId(r.getQuestionId());
                bean.setTestPaperQuestionId(r.getTestPaperQuestionId());
                bean.setScore(r.getScore() + "");
                questionDetailBean.setEduExamUserAnswerDetailBean(bean);
            }
            questionList = questionList.stream().filter(v -> v.getEduExamUserAnswerDetailBean() != null).collect(Collectors.toList());
            // 根据sort正序
            questionList.sort(Comparator.comparing(EduTestPaperQuestionDetailBean::getSort));
            return questionList;
        }
        return Lists.newArrayList();
    }

    @Override
    public void examExcel(Long examId, HttpServletRequest request, HttpServletResponse response) {
        Assert.notNull(examId, "examId cannot be null");
        EduExamUserScopeDto scopeDto = new EduExamUserScopeDto();
        EduExam one = dao.getOne(examId);
        List<EduExamUserDto> eduExamUserDtos = eduExamMapper.getUserRecordName(examId);
        if (CollectionUtils.isEmpty(eduExamUserDtos)){
            throw new IllegalArgumentException("该场考试无参与人员");
        }
        for (EduExamUserDto eduExamUserDto : eduExamUserDtos) {
            EduExamRecord eduExamRecord = eduExamRecordDao.selectByUserIdAndExamIdLatest(eduExamUserDto.getUserId(), eduExamUserDto.getId());
            scopeDto.setName(eduExamUserDto.getName());
            if (eduExamRecord != null) {
                eduExamUserDto.setScore(String.valueOf(eduExamRecord.getScore()));
            }
        }
        scopeDto.setEduExamUserDtos(eduExamUserDtos);
        scopeDto.setPassScore(one.getPassScore());
        scopeDto.setTotalScore(one.getTotalScore());
        Date date = new Date();
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd");
        String file = scopeDto.getName() + "成绩导入模板" + dateFormat.format(date) + ".xls";
        response.setContentType(request.getServletContext().getMimeType(file));
        HSSFWorkbook hwb = null;
        try {
            response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(new String(file.getBytes(), "UTF-8")));
            List<String> cellNames = getCellName();
            Pair<HSSFWorkbook, HSSFSheet> pair = PoiUtil.getExamPoi("成绩导入模板", cellNames);
            addRecordData(scopeDto, pair);
            hwb = pair.getKey();
            hwb.write(response.getOutputStream());
        } catch (IOException e) {
            log.warn("成绩导入模板excel导出失败：" + e.getMessage());
            throw new IllegalArgumentException("excel导出失败，请联系系统管理员");
        } finally {
            if (hwb != null) {
                try {
                    hwb.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }


    private List<String> getCellName() {
        List<String> list = Lists.newArrayList();
        list.add("参与人员");
        list.add("工号");
        list.add("userId");
        list.add("成绩");
        return list;
    }

    private void addRecordData(EduExamUserScopeDto eduExamUserScopeDto, Pair<HSSFWorkbook, HSSFSheet> pair) {
        List<EduExamUserDto> eduExamUserDtos = eduExamUserScopeDto.getEduExamUserDtos();
        CellRangeAddress region = new CellRangeAddress(0, 0, 0, 15);
        HSSFSheet sheet = pair.getValue();
        HSSFWorkbook key = pair.getKey();
        HSSFRow row = sheet.createRow(0);
        Cell cellnew = row.createCell(0);
        cellnew.setCellValue("总分：" + eduExamUserScopeDto.getTotalScore().intValue() + "  合格分数：" + eduExamUserScopeDto.getPassScore().intValue());
        sheet.addMergedRegion(region);
        HSSFCellStyle lockst = key.createCellStyle();
        lockst.setLocked(true);
        HSSFCellStyle unlockst = key.createCellStyle();
        unlockst.setLocked(false);
        for (int i = 0; i < eduExamUserDtos.size(); i++) {
            EduExamUserDto eduExamUserDto = eduExamUserDtos.get(i);
            row = sheet.createRow(i + 2);
            HSSFCell cell0 = row.createCell(0);
            cell0.setCellStyle(unlockst);
            cell0.setCellValue(eduExamUserDto.getRealName());
            //row.createCell(0).setCellValue(eduExamUserDto.getRealName());

            HSSFCell cell1 = row.createCell(1);
            cell1.setCellStyle(unlockst);
            cell1.setCellValue(eduExamUserDto.getCode());
            //row.createCell(1).setCellValue(eduExamUserDto.getCode());
            //row.createCell(2).setCellValue(eduExamUserDto.getUserId().toString());

            HSSFCell cell = row.createCell(2);
            cell.setCellStyle(lockst);
            cell.setCellValue(eduExamUserDto.getUserId().toString());

            if (eduExamUserDto.getScore() != null) {
                HSSFCell cell2 = row.createCell(3);
                cell2.setCellStyle(unlockst);
                cell2.setCellValue(eduExamUserDto.getScore());
            } else {
                HSSFCell cell2 = row.createCell(3);
                cell2.setCellStyle(unlockst);
            }
        }
        sheet.protectSheet(new String("333"));

    }


    @Override
    public Map<String, Object> importExcel(Long examId, MultipartFile file) {
        List<EduExamUserDto> eduExamUserDtoList = new ArrayList<>();
        List<Map> mapList = new ArrayList<>();
        List<Map> Listmap = new ArrayList<>();
        Map<String, Object> map = new HashMap<>();
        Map<String, Object> objectMap = new HashMap<>();
        Workbook workbook = null;
        String filename = file.getOriginalFilename();
        try {
            if (filename != null) {
                if (filename.endsWith(XLS)) {
                    workbook = new HSSFWorkbook(file.getInputStream());
                } else if (filename.endsWith(XLSX)) {
                    workbook = new XSSFWorkbook(file.getInputStream());
                } else {
                    throw new Exception("文件不是Excel文件");
                }
            } else {
                throw new Exception("文件为空");
            }
            // 获取sheet
            Sheet sheet = workbook.getSheetAt(0);

            int lastRowNum = sheet.getLastRowNum();
            if (lastRowNum == 0) {
                throw new Exception("请填写行数");
            }
            List<EduExamUserDto> examUserDtoList = eduExamMapper.getRecordName(examId);
            for (int i = 2; i < lastRowNum + 1; i++) {
                Map lastNum = new HashMap();
                lastNum.put("lastNum", i);
                Listmap.add(lastNum);
                Row row = sheet.getRow(i);
                if (row != null) {
                    // 读取cell单元格内容
                    EduExamUserDto eduExamUserDto = new EduExamUserDto();
                    NumberFormat nf = NumberFormat.getInstance();
                    String userId = null;
                    if (getCellValue(row.getCell(2)).contains(".")) {
                        Cell cell = row.getCell(2);
                        userId = nf.format(cell.getNumericCellValue());
                        if (userId.indexOf(",") >= 0) {
                            userId = userId.replace(",", "");
                        }
                    } else {
                        userId = getCellValue(row.getCell(2));
                    }
                    String cellValue = null;
                    if (getCellValue(row.getCell(3)).contains(".")) {
                        Cell cell1 = row.getCell(3);
                        cellValue = nf.format(cell1.getNumericCellValue());
                        if (cellValue.indexOf(",") >= 0) {
                            cellValue = cellValue.replace(",", "");
                        }
                    } else {
                        cellValue = getCellValue(row.getCell(3));
                    }
                    eduExamUserDto.setUserId(Long.valueOf(userId));
                    if (cellValue.equals("")) {
                        eduExamUserDto.setScore("未考");
                    }
                    if (cellValue.equals("未考")) {
                        eduExamUserDto.setScore("未考");
                    }
                    if (!cellValue.equals("") && !cellValue.equals("未考")) {
                        eduExamUserDto.setScore(cellValue);
                    }
                    for (EduExamUserDto userDto : examUserDtoList) {
                        if (userDto.getUserId().equals(eduExamUserDto.getUserId())) {
                            Map scoreMap = new HashMap();
                            eduExamUserDto.setPassScore(userDto.getPassScore());
                            eduExamUserDto.setTotalScore(userDto.getTotalScore());
                            if (!eduExamUserDto.getScore().equals("未考")) {
//                                Pattern checkNumPattern = Pattern.compile("(^[0-9]+(.[0-9]+)?$)");
                                Pattern checkNumPattern = Pattern.compile("(^-?\\d+(\\.\\d+)?$)");
                                Matcher checkNumMatcher = checkNumPattern.matcher(eduExamUserDto.getScore());
                                if (!checkNumMatcher.matches()){
                                    int toi = i + 1;
                                    scoreMap.put("lastRowNum", "第" + toi + "行失败，失败原因：" + "成绩输入了为非数字");
                                    mapList.add(scoreMap);
                                    break;
                                }
                                if (Float.valueOf(eduExamUserDto.getScore()) > userDto.getTotalScore()) {
                                    int toi = i + 1;
                                    scoreMap.put("lastRowNum", "第" + toi + "行失败，失败原因：" + "成绩不可大于总分");
                                    mapList.add(scoreMap);
                                    break;
                                }
                                if (Float.valueOf(eduExamUserDto.getScore()) < 0) {
                                    int toi = i + 1;
                                    scoreMap.put("lastRowNum", "第" + toi + "行失败，失败原因：" + "成绩不可小于0");
                                    mapList.add(scoreMap);
                                    break;
                                }
                                String zhengshu = "(^-?[1-9](\\d+)?(\\.\\d{1,2})?$)|(^-?0$)|(^-?\\d\\.\\d{1,2}$)";
                                Pattern pattern = Pattern.compile(zhengshu);
                                Matcher matcher = pattern.matcher(eduExamUserDto.getScore());
                                if (!matcher.matches()) {
                                    int toi = i + 1;
                                    scoreMap.put("lastRowNum", "第" + toi + "行失败，失败原因：" + "成绩最多保留两位小数");
                                    mapList.add(scoreMap);
                                    break;
                                }
                                eduExamUserDto.setState(Float.valueOf(eduExamUserDto.getScore()) >= eduExamUserDto.getPassScore() ? 1 : 0);
                            }
                            eduExamUserDto.setId(userDto.getId());
                            eduExamUserDto.setCreateBy(Context.getCurrentUserId());
                            eduExamUserDto.setUpdateBy(Context.getCurrentUserId());
                            eduExamUserDto.setTenentId(Context.getCurrentTenantId());
                            eduExamUserDto.setCreateTime(new Date());
                            eduExamUserDto.setUpdateTime(new Date());
                            eduExamUserDto.setOrgCode(Context.getCurrentOrgCode());
                            eduExamUserDto.setDeleteFlag(String.valueOf(0));
                            if (eduExamUserDto.getState() != null) {
                                if (eduExamUserDto.getState() == 1) {
                                    //考试与培训关联 培训合格标准为考试通过 修改培训结果
                                    validTraining(examId, eduExamUserDto.getUserId());
                                } else {
                                    //第一次导入考试通过分数填错，第二次实际成绩未通过
                                    validTrainingResult(examId, eduExamUserDto.getUserId());
                                }
                            }
                            EduExamRecord eduExamRecord = eduExamRecordsMapper.selectByUserIdandExamId(eduExamUserDto.getUserId(), eduExamUserDto.getId());
                            if (eduExamRecord != null) {
                                eduExamRecord.setDeleteFlag("1");
                                eduExamRecordDao.save(eduExamRecord);
                            }
                            eduExamUserDtoList.add(eduExamUserDto);
                        }
                    }
                }
            }
            // 批量插入
            List<EduExamUserDto> collect = eduExamUserDtoList.stream().collect(Collectors.collectingAndThen(
                    Collectors.toCollection(() -> new TreeSet<>(
                            Comparator.comparing(EduExamUserDto::getUserId))), ArrayList::new));
            if (!CollectionUtils.isEmpty(collect)){
                eduExamRecordsMapper.addBatchMembers(collect);
            }
            //执行条数
            int size = eduExamUserDtoList.size();
            int coll = collect.size();
            //总条数
            int failMap = Listmap.size();
            int fail = failMap - coll - mapList.size();
            map.put("code", 0);
            if (!CollectionUtils.isEmpty(mapList)) {
                objectMap.put("content", "共" + failMap + "行," + "导入成功" + collect.size() + "行," + "失败" + mapList.size() + "行," + "忽略" + fail + "行");
                objectMap.put("fail", mapList);
                objectMap.put("type", 1);
            } else {
                objectMap.put("content", "共" + failMap + "行," + "导入成功" + collect.size() + "行," + "失败0行," + "忽略" + fail + "行");
                objectMap.put("type", 0);
            }
            map.put("data", objectMap);
        } catch (Exception e) {
            map.put("code", -1);
            map.put("data", "导入数据异常");
            throw new RuntimeException();
        }
        return map;
    }

    private Boolean isFinishCourse(Long userId, Long trainingId) {
        List<Long> trainingCoursewares = eduTrainningRecordMapper.selectCoursewareByTrainingId(trainingId);
        List<Long> learns = eduTrainningRecordUserMapper.selectUserLearnCourseware(userId, trainingCoursewares, 1, trainingId);
        //是否学完课程
        return new HashSet<>(learns).containsAll(trainingCoursewares);
    }

    @Override
    public ResultBean myExamList(EduTrainningRecordListBean.SearchEduTrainningRecord condition) {
        condition.setUserId(Context.getCurrentUserId());
        com.baomidou.mybatisplus.extension.plugins.pagination.Page<EduExam> eduExamPage = new com.baomidou.mybatisplus.extension.plugins.pagination.Page<>(condition.getPage() + 1, condition.getSize());
        com.baomidou.mybatisplus.extension.plugins.pagination.Page<ExamUserVo> page = eduExamMapper.myExamList(eduExamPage, condition);
        List<ExamUserVo> records = page.getRecords();
        return ResultBean.pageData(records, page.getTotal());
    }

    @Override
    public void initializedData() {
        List<EduExam> exams = eduExamMapper.selectExam();
        List<EduExam> eduExams = eduExamMapper.selectByRecord();
        if (!eduExams.isEmpty()) {
            eduExamMapper.updateByExamFrom(eduExams);
            ;
        }
        if (!exams.isEmpty()) {
            List<EduExam> eduExamList = exams.stream().filter(edu -> eduExams.stream().noneMatch(e -> e.getId().equals(edu.getId()))).collect(Collectors.toList());
            if (!eduExamList.isEmpty()) {
                eduExamMapper.updateByTrainType(eduExamList);
            }
        }
    }

    @Override
    public EduExamTypeBean getPaper(Long id) {
        EduExamTypeBean bean = new EduExamTypeBean();
        String redisSwers = "ANSWERS_CACHE:" + "swers" + ":" + Context.getCurrentUserId();
        String redisMinutes = "ANSWERS_CACHE:" + "minutes" + ":" + Context.getCurrentUserId();
        String redisUserId = redisClient.get(redisSwers);
        String minutesUserId = redisClient.get(redisMinutes);
        EduExam one = dao.getOne(id);
        if (new Date().after(one.getEndTime())){
            throw new IllegalArgumentException("该考试已结束");
        }
        Integer durationMinutes = one.getDurationMinutes();
        long between = DateUtil.between(new Date(), one.getEndTime(), DateUnit.SECOND);
        if (durationMinutes * 60 >= between) {
            if (StringUtils.isNotBlank(minutesUserId)) {
                EduDoExamResultBean eduDoExamResultBean = JSONUtil.toBean(redisUserId, EduDoExamResultBean.class);
                if (!eduDoExamResultBean.getId().equals(id)) {
                    bean.setType(0);
                } else {
                    bean.setType(1);
                }
            }
        } else {
            if (StringUtils.isNotBlank(redisUserId)) {
                EduDoExamResultBean eduDoExamResultBean = JSONUtil.toBean(redisUserId, EduDoExamResultBean.class);
                if (!eduDoExamResultBean.getId().equals(id)) {
                    bean.setType(0);
                } else {
                    bean.setType(1);
                }
            } else {
                redisClient.removeKey(redisMinutes);
            }
        }
        if (StringUtils.isBlank(redisClient.get(redisMinutes)) && StringUtils.isBlank(redisClient.get(redisSwers))) {
            bean.setType(1);
        }
        return bean;
    }


}


